• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2019 Google LLC.
2 
3 #include "include/core/SkBlurTypes.h"
4 #include "include/core/SkFont.h"
5 #include "include/core/SkFontMetrics.h"
6 #include "include/core/SkMaskFilter.h"
7 #include "include/core/SkPaint.h"
8 #include "include/core/SkSpan.h"
9 #include "include/core/SkString.h"
10 #include "include/core/SkTextBlob.h"
11 #include "include/core/SkTypes.h"
12 #include "include/private/SkTemplates.h"
13 #include "include/private/SkTo.h"
14 #include "modules/skparagraph/include/DartTypes.h"
15 #include "modules/skparagraph/include/Metrics.h"
16 #include "modules/skparagraph/include/ParagraphPainter.h"
17 #include "modules/skparagraph/include/ParagraphStyle.h"
18 #include "modules/skparagraph/include/TextShadow.h"
19 #include "modules/skparagraph/include/TextStyle.h"
20 #include "modules/skparagraph/src/Decorations.h"
21 #include "modules/skparagraph/src/ParagraphImpl.h"
22 #include "modules/skparagraph/src/ParagraphPainterImpl.h"
23 #include "modules/skparagraph/src/TextLine.h"
24 #include "modules/skshaper/include/SkShaper.h"
25 
26 #include <algorithm>
27 #include <iterator>
28 #include <limits>
29 #include <map>
30 #include <memory>
31 #include <tuple>
32 #include <type_traits>
33 #include <utility>
34 
35 #ifdef OHOS_SUPPORT
36 #include <cstddef>
37 #include <numeric>
38 #include "log.h"
39 #include "modules/skparagraph/src/RunBaseImpl.h"
40 #include "modules/skparagraph/src/TextLineBaseImpl.h"
41 #include "TextParameter.h"
42 #include "TextLineJustify.h"
43 #endif
44 
45 namespace skia {
46 namespace textlayout {
47 #define MAX_INT_VALUE 0x7FFFFFFF
48 
49 #ifdef OHOS_SUPPORT
50 #define EMOJI_UNICODE_START 0x1F300
51 #define EMOJI_UNICODE_END 0x1F9EF
52 #define EMOJI_WIDTH 4
53 #endif
54 
55 namespace {
56 
57 // TODO: deal with all the intersection functionality
intersected(const TextRange & a,const TextRange & b)58 TextRange intersected(const TextRange& a, const TextRange& b) {
59     if (a.start == b.start && a.end == b.end) return a;
60     auto begin = std::max(a.start, b.start);
61     auto end = std::min(a.end, b.end);
62     return end >= begin ? TextRange(begin, end) : EMPTY_TEXT;
63 }
64 
littleRound(SkScalar a)65 SkScalar littleRound(SkScalar a) {
66     // This rounding is done to match Flutter tests. Must be removed..
67   return SkScalarRoundToScalar(a * 100.0)/100.0;
68 }
69 
operator *(const TextRange & a,const TextRange & b)70 TextRange operator*(const TextRange& a, const TextRange& b) {
71     if (a.start == b.start && a.end == b.end) return a;
72     auto begin = std::max(a.start, b.start);
73     auto end = std::min(a.end, b.end);
74     return end > begin ? TextRange(begin, end) : EMPTY_TEXT;
75 }
76 
compareRound(SkScalar a,SkScalar b,bool applyRoundingHack)77 int compareRound(SkScalar a, SkScalar b, bool applyRoundingHack) {
78     // There is a rounding error that gets bigger when maxWidth gets bigger
79     // VERY long zalgo text (> 100000) on a VERY long line (> 10000)
80     // Canvas scaling affects it
81     // Letter spacing affects it
82     // It has to be relative to be useful
83     auto base = std::max(SkScalarAbs(a), SkScalarAbs(b));
84     auto diff = SkScalarAbs(a - b);
85     if (nearlyZero(base) || diff / base < 0.001f) {
86         return 0;
87     }
88 
89     auto ra = a;
90     auto rb = b;
91 
92     if (applyRoundingHack) {
93         ra = littleRound(a);
94         rb = littleRound(b);
95     }
96     if (ra < rb) {
97         return -1;
98     } else {
99         return 1;
100     }
101 }
102 
103 #ifdef USE_SKIA_TXT
IsRSFontEquals(const RSFont & font0,const RSFont & font1)104 bool IsRSFontEquals(const RSFont& font0, const RSFont& font1) {
105     auto f0 = const_cast<RSFont&>(font0);
106     auto f1 = const_cast<RSFont&>(font1);
107     return f0.GetTypeface().get() == f1.GetTypeface().get() &&
108         f0.GetSize() == f1.GetSize() &&
109         f0.GetScaleX() == f1.GetScaleX() &&
110         f0.GetSkewX() == f1.GetSkewX() &&
111         f0.GetEdging() == f1.GetEdging() &&
112         f0.GetHinting() == f1.GetHinting();
113 }
114 #endif
115 
116 #ifdef OHOS_SUPPORT
117 #ifdef USE_SKIA_TXT
GetTextBlobSkTightBound(std::shared_ptr<RSTextBlob> blob,float offsetX,float offsetY,const SkRect & clipRect)118 SkRect GetTextBlobSkTightBound(std::shared_ptr<RSTextBlob> blob, float offsetX, float offsetY, const SkRect& clipRect)
119 {
120     if (blob == nullptr || blob->Bounds() == nullptr) {
121         return SkRect::MakeEmpty();
122     }
123 
124     RSRect bound = *blob->Bounds();
125     bound.Offset(offsetX, offsetY);
126     if (!clipRect.isEmpty()) {
127         bound.left_ = std::max(bound.left_, clipRect.fLeft);
128         bound.right_ = std::min(bound.right_, clipRect.fRight);
129     }
130     return SkRect::MakeLTRB(bound.left_, bound.top_, bound.right_, bound.bottom_);
131 }
132 #else
GetTextBlobSkTightBound(sk_sp<SkTextBlob> blob,float offsetX,float offsetY,const SkRect & clipRect)133 SkRect GetTextBlobSkTightBound(sk_sp<SkTextBlob> blob, float offsetX, float offsetY, const SkRect& clipRect)
134 {
135     if (blob == nullptr) {
136         return SkRect::MakeEmpty();
137     }
138 
139     SkRect bound = blob->bounds();
140     if (!clipRect.isEmpty()) {
141         bound.fLeft = std::max(bound.fLeft, clipRect.fLeft);
142         bound.fRight = std::min(bound.fRight, clipRect.fRight);
143     }
144     bound.offset(offsetX, offsetY);
145     return bound;
146 }
147 #endif
148 #endif
149 }  // namespace
150 
TextLine(ParagraphImpl * owner,SkVector offset,SkVector advance,BlockRange blocks,TextRange textExcludingSpaces,TextRange text,TextRange textIncludingNewlines,ClusterRange clusters,ClusterRange clustersWithGhosts,SkScalar widthWithSpaces,InternalLineMetrics sizes)151 TextLine::TextLine(ParagraphImpl* owner,
152                    SkVector offset,
153                    SkVector advance,
154                    BlockRange blocks,
155                    TextRange textExcludingSpaces,
156                    TextRange text,
157                    TextRange textIncludingNewlines,
158                    ClusterRange clusters,
159                    ClusterRange clustersWithGhosts,
160                    SkScalar widthWithSpaces,
161                    InternalLineMetrics sizes)
162         : fOwner(owner)
163         , fBlockRange(blocks)
164         , fTextExcludingSpaces(textExcludingSpaces)
165         , fText(text)
166         , fTextIncludingNewlines(textIncludingNewlines)
167         , fClusterRange(clusters)
168         , fGhostClusterRange(clustersWithGhosts)
169         , fRunsInVisualOrder()
170         , fAdvance(advance)
171         , fOffset(offset)
172         , fShift(0.0)
173         , fWidthWithSpaces(widthWithSpaces)
174         , fEllipsis(nullptr)
175         , fSizes(sizes)
176         , fHasBackground(false)
177         , fHasShadows(false)
178         , fHasDecorations(false)
179         , fIsArcText(false)
180         , fArcTextState(false)
181         , fAscentStyle(LineMetricStyle::CSS)
182         , fDescentStyle(LineMetricStyle::CSS)
183         , fTextBlobCachePopulated(false) {
184     // Reorder visual runs
185     auto& start = owner->cluster(fGhostClusterRange.start);
186     auto& end = owner->cluster(fGhostClusterRange.end - 1);
187     size_t numRuns = end.runIndex() - start.runIndex() + 1;
188 
189     for (BlockIndex index = fBlockRange.start; index < fBlockRange.end; ++index) {
190         auto b = fOwner->styles().begin() + index;
191         if (b->fStyle.hasBackground()) {
192             fHasBackground = true;
193         }
194 
195 #ifdef OHOS_SUPPORT
196         if (b->fStyle.getDecorationType() != TextDecoration::kNoDecoration &&
197             b->fStyle.getDecorationThicknessMultiplier() > 0) {
198 #else
199         if (b->fStyle.getDecorationType() != TextDecoration::kNoDecoration) {
200 #endif
201             fHasDecorations = true;
202         }
203         if (b->fStyle.getShadowNumber() > 0) {
204             fHasShadows = true;
205         }
206     }
207 
208     // Get the logical order
209 
210     // This is just chosen to catch the common/fast cases. Feel free to tweak.
211     constexpr int kPreallocCount = 4;
212     SkAutoSTArray<kPreallocCount, SkUnicode::BidiLevel> runLevels(numRuns);
213     std::vector<RunIndex> placeholdersInOriginalOrder;
214     size_t runLevelsIndex = 0;
215     // Placeholders must be laid out using the original order in which they were added
216     // in the input. The API does not provide a way to indicate that a placeholder
217     // position was moved due to bidi reordering.
218     for (auto runIndex = start.runIndex(); runIndex <= end.runIndex(); ++runIndex) {
219         auto& run = fOwner->run(runIndex);
220         runLevels[runLevelsIndex++] = run.fBidiLevel;
221         fMaxRunMetrics.add(
222             InternalLineMetrics(run.correctAscent(), run.correctDescent(), run.fFontMetrics.fLeading));
223         if (run.isPlaceholder()) {
224             placeholdersInOriginalOrder.push_back(runIndex);
225         }
226     }
227     SkASSERT(runLevelsIndex == numRuns);
228 
229     SkAutoSTArray<kPreallocCount, int32_t> logicalOrder(numRuns);
230 
231     // TODO: hide all these logic in SkUnicode?
232     fOwner->getUnicode()->reorderVisual(runLevels.data(), numRuns, logicalOrder.data());
233     auto firstRunIndex = start.runIndex();
234     auto placeholderIter = placeholdersInOriginalOrder.begin();
235     for (auto index : logicalOrder) {
236         auto runIndex = firstRunIndex + index;
237         if (fOwner->run(runIndex).isPlaceholder()) {
238             fRunsInVisualOrder.push_back(*placeholderIter++);
239         } else {
240             fRunsInVisualOrder.push_back(runIndex);
241         }
242     }
243 
244     fTextRangeReplacedByEllipsis = EMPTY_RANGE;
245     fEllipsisIndex = EMPTY_INDEX;
246 #ifdef OHOS_SUPPORT
247     fHyphenIndex = EMPTY_INDEX;
248 #endif
249     fLastClipRunLtr = false;
250 }
251 
252 void TextLine::paint(ParagraphPainter* painter, const RSPath* path, SkScalar hOffset, SkScalar vOffset) {
253     prepareRoundRect();
254     fIsArcText = true;
255     if (pathParameters.hOffset != hOffset || pathParameters.vOffset != vOffset) {
256         fTextBlobCachePopulated = false;
257     }
258     pathParameters.recordPath = path;
259     pathParameters.hOffset = hOffset;
260     pathParameters.vOffset = vOffset;
261     this->ensureTextBlobCachePopulated();
262     for (auto& record : fTextBlobCache) {
263         record.paint(painter);
264     }
265 }
266 
267 void TextLine::paint(ParagraphPainter* painter, SkScalar x, SkScalar y) {
268     prepareRoundRect();
269     fIsArcText = false;
270 #ifdef OHOS_SUPPORT
271     this->iterateThroughVisualRuns(EllipsisReadStrategy::READ_REPLACED_WORD, true,
272 #else
273     this->iterateThroughVisualRuns(false,
274 #endif
275         [painter, x, y, this]
276         (const Run* run, SkScalar runOffsetInLine, TextRange textRange, SkScalar* runWidthInLine) {
277             *runWidthInLine = this->iterateThroughSingleRunByStyles(
278             TextAdjustment::GlyphCluster, run, runOffsetInLine, textRange, StyleType::kBackground,
279             [painter, x, y, run, this](TextRange textRange, const TextStyle& style, const ClipContext& context) {
280                 if (fHasBackground) {
281                     this->paintBackground(painter, x, y, textRange, style, context);
282                 }
283                 paintRoundRect(painter, x, y, run);
284             });
285         return true;
286         });
287 
288     if (fHasShadows) {
289 #ifdef OHOS_SUPPORT
290         this->iterateThroughVisualRuns(EllipsisReadStrategy::READ_REPLACED_WORD, false,
291 #else
292         this->iterateThroughVisualRuns(false,
293 #endif
294             [painter, x, y, this]
295             (const Run* run, SkScalar runOffsetInLine, TextRange textRange, SkScalar* runWidthInLine) {
296             *runWidthInLine = this->iterateThroughSingleRunByStyles(
297                 TextAdjustment::GlyphCluster, run, runOffsetInLine, textRange, StyleType::kShadow,
298                 [painter, x, y, this]
299                 (TextRange textRange, const TextStyle& style, const ClipContext& context) {
300                     this->paintShadow(painter, x, y, textRange, style, context);
301                 });
302             return true;
303             });
304     }
305 
306     this->ensureTextBlobCachePopulated();
307 
308     for (auto& record : fTextBlobCache) {
309         record.paint(painter, x, y);
310     }
311 
312     if (fHasDecorations) {
313 #ifdef OHOS_SUPPORT
314         this->fDecorationContext = {0.0f, 0.0f, 0.0f};
315         // 16 is default value in placeholder-only scenario, calculated by the fontsize 14.
316         SkScalar maxLineHeightWithoutPlaceholder = 16;
317         this->iterateThroughVisualRuns(EllipsisReadStrategy::DEFAULT, true,
318             [painter, x, y, this, &maxLineHeightWithoutPlaceholder]
319             (const Run* run, SkScalar runOffsetInLine, TextRange textRange, SkScalar* runWidthInLine) {
320                 *runWidthInLine = this->iterateThroughSingleRunByStyles(
321                     TextAdjustment::GlyphCluster, run, runOffsetInLine, textRange,
322                     StyleType::kDecorations, [painter, x, y, this, &maxLineHeightWithoutPlaceholder]
323                     (TextRange textRange, const TextStyle& style, const ClipContext& context) {
324                     if (style.getDecoration().fType == TextDecoration::kUnderline) {
325                         SkScalar tmpThick = this->calculateThickness(style, context);
326                         fDecorationContext.thickness = fDecorationContext.thickness > tmpThick ?
327                             fDecorationContext.thickness : tmpThick;
328                     }
329                     auto cur = context.run;
330                     if (cur != nullptr && !cur->isPlaceholder()) {
331                         SkScalar height = round(cur->correctDescent() - cur->correctAscent() + cur->correctLeading());
332                         maxLineHeightWithoutPlaceholder = maxLineHeightWithoutPlaceholder < height ?
333                             height : maxLineHeightWithoutPlaceholder;
334                     }
335                 });
336                 return true;
337         });
338         // 16% of row height wihtout placeholder.
339         fDecorationContext.underlinePosition = maxLineHeightWithoutPlaceholder * 0.16 + baseline();
340         fDecorationContext.textBlobTop = maxLineHeightWithoutPlaceholder * 0.16;
341 #endif
342 
343 #ifdef OHOS_SUPPORT
344         this->iterateThroughVisualRuns(EllipsisReadStrategy::DEFAULT, true,
345 #else
346         this->iterateThroughVisualRuns(false,
347 #endif
348             [painter, x, y, this]
349             (const Run* run, SkScalar runOffsetInLine, TextRange textRange, SkScalar* runWidthInLine) {
350                 *runWidthInLine = this->iterateThroughSingleRunByStyles(
351                 TextAdjustment::GlyphCluster, run, runOffsetInLine, textRange, StyleType::kDecorations,
352                 [painter, x, y, this]
353                 (TextRange textRange, const TextStyle& style, const ClipContext& context) {
354                     this->paintDecorations(painter, x, y, textRange, style, context);
355                 });
356                 return true;
357         });
358     }
359 }
360 
361 bool TextLine::hasBackgroundRect(const RoundRectAttr& attr) {
362     return attr.roundRectStyle.color != 0 && attr.rect.width() > 0;
363 }
364 
365 void TextLine::computeRoundRect(int& index, int& preIndex, std::vector<Run*>& groupRuns, Run* run) {
366     int runCount = roundRectAttrs.size();
367     if (index >= runCount) {
368         return;
369     }
370 
371     bool leftRound = false;
372     bool rightRound = false;
373     if (hasBackgroundRect(roundRectAttrs[index])) {
374         int styleId = roundRectAttrs[index].styleId;
375         // index - 1 is previous index, -1 is the invalid styleId
376         int preStyleId = index == 0 ? -1 : roundRectAttrs[index - 1].styleId;
377         // runCount - 1 is the last run index, index + 1 is next run index, -1 is the invalid styleId
378         int nextStyleId = index == runCount - 1 ? -1 : roundRectAttrs[index + 1].styleId;
379         // index - preIndex > 1 means the left run has no background rect
380         leftRound = (preIndex < 0 || index - preIndex > 1 || preStyleId != styleId);
381         // runCount - 1 is the last run index
382         rightRound = (index == runCount - 1 || !hasBackgroundRect(roundRectAttrs[index + 1]) ||
383             nextStyleId != styleId);
384         preIndex = index;
385         groupRuns.push_back(run);
386     } else if (!groupRuns.empty()) {
387         groupRuns.erase(groupRuns.begin(), groupRuns.end());
388     }
389     if (leftRound && rightRound) {
390         run->setRoundRectType(RoundRectType::ALL);
391     } else if (leftRound) {
392         run->setRoundRectType(RoundRectType::LEFT_ONLY);
393     } else if (rightRound) {
394         run->setRoundRectType(RoundRectType::RIGHT_ONLY);
395     } else {
396         run->setRoundRectType(RoundRectType::NONE);
397     }
398 
399     if (rightRound && !groupRuns.empty()) {
400         double maxRoundRectRadius = MAX_INT_VALUE;
401         double minTop = MAX_INT_VALUE;
402         double maxBottom = 0;
403         for (auto &gRun : groupRuns) {
404             RoundRectAttr& attr = roundRectAttrs[gRun->getIndexInLine()];
405             maxRoundRectRadius = std::fmin(std::fmin(attr.rect.width(), attr.rect.height()), maxRoundRectRadius);
406             minTop = std::fmin(minTop, attr.rect.top());
407             maxBottom = std::fmax(maxBottom, attr.rect.bottom());
408         }
409         for (auto &gRun : groupRuns) {
410             gRun->setMaxRoundRectRadius(maxRoundRectRadius);
411             gRun->setTopInGroup(minTop - gRun->offset().y());
412             gRun->setBottomInGroup(maxBottom - gRun->offset().y());
413         }
414         groupRuns.erase(groupRuns.begin(), groupRuns.end());
415     }
416     index++;
417 }
418 
419 void TextLine::prepareRoundRect() {
420     roundRectAttrs.clear();
421 #ifdef OHOS_SUPPORT
422         this->iterateThroughVisualRuns(EllipsisReadStrategy::DEFAULT, true,
423 #else
424         this->iterateThroughVisualRuns(true,
425 #endif
426         [this](const Run* run, SkScalar runOffsetInLine, TextRange textRange, SkScalar* runWidthInLine) {
427             *runWidthInLine = this->iterateThroughSingleRunByStyles(
428             TextAdjustment::GlyphCluster, run, runOffsetInLine, textRange, StyleType::kBackground,
429             [run, this](TextRange textRange, const TextStyle& style, const ClipContext& context) {
430                 roundRectAttrs.push_back({style.getStyleId(), style.getBackgroundRect(), context.clip});
431             });
432             return true;
433         });
434 
435     std::vector<Run*> groupRuns;
436     int index = 0;
437     int preIndex = -1;
438     for (auto& runIndex : fRunsInVisualOrder) {
439         auto run = &this->fOwner->run(runIndex);
440         run->setIndexInLine(static_cast<size_t>(index));
441         computeRoundRect(index, preIndex, groupRuns, run);
442     }
443 }
444 
445 void TextLine::ensureTextBlobCachePopulated() {
446     if (fTextBlobCachePopulated && fArcTextState == fIsArcText) {
447         return;
448     }
449     fTextBlobCache.clear();
450     if (fBlockRange.width() == 1 &&
451         fRunsInVisualOrder.size() == 1 &&
452         fEllipsis == nullptr &&
453 #ifdef OHOS_SUPPORT
454         fHyphenRun == nullptr &&
455 #endif
456         fOwner->run(fRunsInVisualOrder[0]).placeholderStyle() == nullptr) {
457         if (fClusterRange.width() == 0) {
458             return;
459         }
460         // Most common and most simple case
461         const auto& style = fOwner->block(fBlockRange.start).fStyle;
462         const auto& run = fOwner->run(fRunsInVisualOrder[0]);
463         auto clip = SkRect::MakeXYWH(0.0f, this->sizes().runTop(&run, this->fAscentStyle),
464                                      fAdvance.fX,
465                                      run.calculateHeight(this->fAscentStyle, this->fDescentStyle));
466 
467         auto& start = fOwner->cluster(fClusterRange.start);
468         auto& end = fOwner->cluster(fClusterRange.end - 1);
469         SkASSERT(start.runIndex() == end.runIndex());
470         GlyphRange glyphs;
471         if (run.leftToRight()) {
472             glyphs = GlyphRange(start.startPos(),
473                                 end.isHardBreak() ? end.startPos() : end.endPos());
474         } else {
475             glyphs = GlyphRange(end.startPos(),
476                                 start.isHardBreak() ? start.startPos() : start.endPos());
477         }
478         ClipContext context = {/*run=*/&run,
479                                /*pos=*/glyphs.start,
480                                /*size=*/glyphs.width(),
481                                /*fTextShift=*/-run.positionX(glyphs.start), // starting position
482                                /*clip=*/clip,                               // entire line
483                                /*fExcludedTrailingSpaces=*/0.0f,            // no need for that
484                                /*clippingNeeded=*/false};                   // no need for that
485         this->buildTextBlob(fTextExcludingSpaces, style, context);
486     } else {
487 #ifdef OHOS_SUPPORT
488         this->iterateThroughVisualRuns(EllipsisReadStrategy::READ_ELLIPSIS_WORD, false,
489 #else
490         this->iterateThroughVisualRuns(false,
491 #endif
492            [this](const Run* run,
493                   SkScalar runOffsetInLine,
494                   TextRange textRange,
495                   SkScalar* runWidthInLine) {
496                if (run->placeholderStyle() != nullptr) {
497                    *runWidthInLine = run->advance().fX;
498                    return true;
499                }
500                *runWidthInLine = this->iterateThroughSingleRunByStyles(
501                    TextAdjustment::GlyphCluster,
502                    run,
503                    runOffsetInLine,
504                    textRange,
505                    StyleType::kForeground,
506                    [this](TextRange textRange, const TextStyle& style, const ClipContext& context) {
507                        this->buildTextBlob(textRange, style, context);
508                    });
509                return true;
510            });
511     }
512     fTextBlobCachePopulated = true;
513     fArcTextState = fIsArcText;
514     pathParameters.recordPath = nullptr;
515 }
516 
517 void TextLine::format(TextAlign align, SkScalar maxWidth, EllipsisModal ellipsisModal) {
518     SkScalar delta = maxWidth - this->widthWithEllipsisSpaces();
519 #ifdef OHOS_SUPPORT
520     delta = (delta < 0) ? 0 : delta;
521 #else
522     if (delta <= 0) {
523         return;
524     }
525 #endif
526     // We do nothing for left align
527     if (align == TextAlign::kJustify) {
528         if (!this->endsWithHardLineBreak()) {
529             this->justify(maxWidth);
530         } else if (fOwner->paragraphStyle().getTextDirection() == TextDirection::kRtl) {
531             // Justify -> Right align
532             fShift = delta;
533         }
534     } else if (align == TextAlign::kRight) {
535 #ifdef OHOS_SUPPORT
536         auto lastCluster = fOwner->clusters()[fGhostClusterRange.end - 1];
537         bool isRTLWhiteSpace = lastCluster.isWhitespaceBreak() && !lastCluster.run().leftToRight();
538         // Only be entered when the text alignment direction is RTL and the last character is an RTL whitespace
539         if (fOwner->paragraphStyle().getTextDirection() == TextDirection::kRtl && isRTLWhiteSpace) {
540             fShift = maxWidth - this->width();
541         } else {
542             fShift = delta;
543         }
544 #else
545         fShift = delta;
546 #endif
547     } else if (align == TextAlign::kCenter) {
548         fShift = delta / 2;
549     }
550 }
551 
552 #ifdef OHOS_SUPPORT
553 SkScalar TextLine::autoSpacing() {
554     if (!TextParameter::GetAutoSpacingEnable()) {
555         return 0;
556     }
557     SkScalar spacing = 0.0;
558     auto prevCluster = fOwner->cluster(fClusterRange.start);
559     for (auto clusterIndex = fClusterRange.start + 1; clusterIndex < fClusterRange.end; ++clusterIndex) {
560         auto prevSpacing = spacing;
561         auto& cluster = fOwner->cluster(clusterIndex);
562         spacing += cluster.needAutoSpacing() ? prevCluster.getFontSize() / AUTO_SPACING_WIDTH_RATIO : 0;
563         spacingCluster(&cluster, spacing, prevSpacing);
564         prevCluster = cluster;
565     }
566     this->fWidthWithSpaces += spacing;
567     this->fAdvance.fX += spacing;
568     return spacing;
569 }
570 #endif
571 
572 void TextLine::scanStyles(StyleType styleType, const RunStyleVisitor& visitor) {
573     if (this->empty()) {
574         return;
575     }
576 
577 #ifdef OHOS_SUPPORT
578     this->iterateThroughVisualRuns(EllipsisReadStrategy::READ_REPLACED_WORD, false,
579 #else
580     this->iterateThroughVisualRuns(false,
581 #endif
582             [this, visitor, styleType](
583                     const Run* run, SkScalar runOffset, TextRange textRange, SkScalar* width) {
584                 *width = this->iterateThroughSingleRunByStyles(
585                         TextAdjustment::GlyphCluster,
586                         run,
587                         runOffset,
588                         textRange,
589                         styleType,
590                         [visitor](TextRange textRange,
591                                   const TextStyle& style,
592                                   const ClipContext& context) {
593                             visitor(textRange, style, context);
594                         });
595                 return true;
596             });
597 }
598 
599 SkRect TextLine::extendHeight(const ClipContext& context) const {
600     SkRect result = context.clip;
601     result.fBottom += std::max(this->fMaxRunMetrics.height() - this->height(), 0.0f);
602     return result;
603 }
604 
605 void TextLine::buildTextBlob(TextRange textRange, const TextStyle& style, const ClipContext& context) {
606     if (context.run->placeholderStyle() != nullptr) {
607         return;
608     }
609 
610     fTextBlobCache.emplace_back();
611     TextBlobRecord& record = fTextBlobCache.back();
612 
613     if (style.hasForeground()) {
614         record.fPaint = style.getForegroundPaintOrID();
615     } else {
616         std::get<SkPaint>(record.fPaint).setColor(style.getColor());
617     }
618     record.fVisitor_Run = context.run;
619     record.fVisitor_Pos = context.pos;
620     record.fVisitor_Size = context.size;
621 
622     // TODO: This is the change for flutter, must be removed later
623 #ifndef USE_SKIA_TXT
624     SkTextBlobBuilder builder;
625 #else
626     RSTextBlobBuilder builder;
627 #endif
628     if (pathParameters.recordPath) {
629         context.run->copyTo(builder,
630                             pathParameters.recordPath,
631                             pathParameters.hOffset,
632                             pathParameters.vOffset,
633                             context.fTextShift,
634                             SkToU32(context.pos),
635                             context.size);
636     } else {
637         context.run->copyTo(builder, SkToU32(context.pos), context.size);
638     }
639 #ifdef OHOS_SUPPORT
640     // when letterspacing < 0, it causes the font is cliped. so the record fClippingNeeded is set false
641 #else
642     record.fClippingNeeded = context.clippingNeeded;
643 #endif
644     if (context.clippingNeeded) {
645         record.fClipRect = extendHeight(context).makeOffset(this->offset());
646     } else {
647         record.fClipRect = context.clip.makeOffset(this->offset());
648     }
649 
650     SkASSERT(nearlyEqual(context.run->baselineShift(), style.getBaselineShift()));
651     SkScalar correctedBaseline = SkScalarFloorToScalar(this->baseline() + style.getBaselineShift() +  0.5);
652 #ifndef USE_SKIA_TXT
653     record.fBlob = builder.make();
654     if (record.fBlob != nullptr) {
655         record.fBounds.joinPossiblyEmptyRect(record.fBlob->bounds());
656     }
657 #else
658     record.fBlob = builder.Make();
659     if (record.fBlob != nullptr) {
660         auto bounds = record.fBlob->Bounds();
661         if (bounds) {
662             record.fBounds.joinPossiblyEmptyRect(SkRect::MakeLTRB(
663                 bounds->left_, bounds->top_, bounds->right_, bounds->bottom_
664             ));
665         }
666     }
667 #endif
668 
669     record.fOffset = SkPoint::Make(this->offset().fX + context.fTextShift,
670 #ifdef OHOS_SUPPORT
671         this->offset().fY + correctedBaseline - (context.run ? context.run->fCompressionBaselineShift : 0));
672 #else
673                                    this->offset().fY + correctedBaseline);
674 #endif
675 #ifdef OHOS_SUPPORT
676 #ifndef USE_SKIA_TXT
677     SkFont font;
678 #else
679     RSFont font;
680 #endif
681     if (record.fBlob != nullptr && record.fVisitor_Run != nullptr) {
682         font = record.fVisitor_Run->font();
683         if (font.GetTypeface() != nullptr &&
684             (font.GetTypeface()->GetFamilyName().find("Emoji") != std::string::npos ||
685             font.GetTypeface()->GetFamilyName().find("emoji") != std::string::npos)) {
686                 record.fBlob->SetEmoji(true);
687         }
688     }
689 #endif
690 }
691 
692 void TextLine::TextBlobRecord::paint(ParagraphPainter* painter, SkScalar x, SkScalar y) {
693     if (fClippingNeeded) {
694         painter->save();
695         painter->clipRect(fClipRect.makeOffset(x, y));
696     }
697     painter->drawTextBlob(fBlob, x + fOffset.x(), y + fOffset.y(), fPaint);
698     if (fClippingNeeded) {
699         painter->restore();
700     }
701 }
702 
703 void TextLine::TextBlobRecord::paint(ParagraphPainter* painter) {
704     if (fClippingNeeded) {
705         painter->save();
706     }
707     painter->drawTextBlob(fBlob, 0, 0, fPaint);
708     if (fClippingNeeded) {
709         painter->restore();
710     }
711 }
712 
713 void TextLine::paintBackground(ParagraphPainter* painter,
714                                SkScalar x,
715                                SkScalar y,
716                                TextRange textRange,
717                                const TextStyle& style,
718                                const ClipContext& context) const {
719     if (style.hasBackground()) {
720         painter->drawRect(context.clip.makeOffset(this->offset() + SkPoint::Make(x, y)),
721                           style.getBackgroundPaintOrID());
722     }
723 }
724 
725 void TextLine::paintRoundRect(ParagraphPainter* painter, SkScalar x, SkScalar y, const Run* run) const {
726     size_t index = run->getIndexInLine();
727     if (index >= roundRectAttrs.size()) {
728         return;
729     }
730 
731     const RoundRectAttr& attr = roundRectAttrs[index];
732     if (attr.roundRectStyle.color == 0) {
733         return;
734     }
735 
736     SkScalar ltRadius = 0.0f;
737     SkScalar rtRadius = 0.0f;
738     SkScalar rbRadius = 0.0f;
739     SkScalar lbRadius = 0.0f;
740     RoundRectType rType = run->getRoundRectType();
741     if (rType == RoundRectType::ALL || rType == RoundRectType::LEFT_ONLY) {
742         ltRadius = std::fmin(attr.roundRectStyle.leftTopRadius, run->getMaxRoundRectRadius());
743         lbRadius = std::fmin(attr.roundRectStyle.leftBottomRadius, run->getMaxRoundRectRadius());
744     }
745     if (rType == RoundRectType::ALL || rType == RoundRectType::RIGHT_ONLY) {
746         rtRadius = std::fmin(attr.roundRectStyle.rightTopRadius, run->getMaxRoundRectRadius());
747         rbRadius = std::fmin(attr.roundRectStyle.rightBottomRadius, run->getMaxRoundRectRadius());
748     }
749     const SkVector radii[4] = {{ltRadius, ltRadius}, {rtRadius, rtRadius}, {rbRadius, rbRadius}, {lbRadius, lbRadius}};
750     SkRect skRect(SkRect::MakeLTRB(attr.rect.left(), run->getTopInGroup(), attr.rect.right(),
751         run->getBottomInGroup()));
752     SkRRect skRRect;
753     skRRect.setRectRadii(skRect, radii);
754     skRRect.offset(x + this->offset().x(), y + this->offset().y());
755     painter->drawRRect(skRRect, attr.roundRectStyle.color);
756 }
757 
758 void TextLine::paintShadow(ParagraphPainter* painter,
759                            SkScalar x,
760                            SkScalar y,
761                            TextRange textRange,
762                            const TextStyle& style,
763                            const ClipContext& context) const {
764     SkScalar correctedBaseline = SkScalarFloorToScalar(this->baseline() + style.getBaselineShift() + 0.5);
765 
766     for (TextShadow shadow : style.getShadows()) {
767         if (!shadow.hasShadow()) continue;
768 
769 #ifndef USE_SKIA_TXT
770         SkTextBlobBuilder builder;
771 #else
772         RSTextBlobBuilder builder;
773 #endif
774         context.run->copyTo(builder, context.pos, context.size);
775 
776         if (context.clippingNeeded) {
777             painter->save();
778             SkRect clip = extendHeight(context);
779             clip.offset(x, y);
780             clip.offset(this->offset());
781             painter->clipRect(clip);
782         }
783 #ifndef USE_SKIA_TXT
784         auto blob = builder.make();
785 #else
786         auto blob = builder.Make();
787 #endif
788         painter->drawTextShadow(blob,
789             x + this->offset().fX + shadow.fOffset.x() + context.fTextShift,
790 #ifdef OHOS_SUPPORT
791             y + this->offset().fY + shadow.fOffset.y() + correctedBaseline -
792                 (context.run ? context.run->fCompressionBaselineShift : 0),
793 #else
794             y + this->offset().fY + shadow.fOffset.y() + correctedBaseline,
795 #endif
796             shadow.fColor,
797             SkDoubleToScalar(shadow.fBlurSigma));
798         if (context.clippingNeeded) {
799             painter->restore();
800         }
801     }
802 }
803 
804 SkScalar TextLine::calculateThickness(const TextStyle& style, const ClipContext& content)
805 {
806     Decorations decoration;
807     return decoration.calculateThickness(style, content);
808 }
809 
810 void TextLine::paintDecorations(ParagraphPainter* painter, SkScalar x, SkScalar y, TextRange textRange, const TextStyle& style, const ClipContext& context) const {
811     ParagraphPainterAutoRestore ppar(painter);
812     painter->translate(x + this->offset().fX, y + this->offset().fY + style.getBaselineShift());
813     Decorations decorations;
814     decorations.setDecorationContext(fDecorationContext);
815     SkScalar correctedBaseline = SkScalarFloorToScalar(-this->sizes().rawAscent() + style.getBaselineShift() + 0.5);
816     decorations.paint(painter, style, context, correctedBaseline);
817 }
818 
819 #ifdef OHOS_SUPPORT
820 void TextLine::justify(SkScalar maxWidth)
821 {
822     TextLineJustify textLineJustify(*this);
823     if (textLineJustify.justify(maxWidth)) {
824         this->fWidthWithSpaces += (maxWidth - this->widthWithoutEllipsis());
825         this->fAdvance.fX = maxWidth;
826     }
827 }
828 
829 void TextLine::updateClusterOffsets(const Cluster* cluster, SkScalar shift, SkScalar prevShift)
830 {
831     this->shiftCluster(cluster, shift, prevShift);
832 }
833 
834 void TextLine::justifyUpdateRtlWidth(const SkScalar maxWidth, const SkScalar textLen)
835 {
836     if (this->fOwner->paragraphStyle().getTextDirection() == TextDirection::kRtl) {
837         // Justify -> Right align
838         this->fShift = maxWidth - textLen;
839     }
840 }
841 #else
842 void TextLine::justify(SkScalar maxWidth) {
843     int whitespacePatches = 0;
844     SkScalar textLen = 0;
845     bool whitespacePatch = false;
846     // Take leading whitespaces width but do not increment a whitespace patch number
847     bool leadingWhitespaces = false;
848     this->iterateThroughClustersInGlyphsOrder(false, false,
849         [&](const Cluster* cluster, ClusterIndex index, bool ghost) {
850             if (cluster->isWhitespaceBreak()) {
851                 if (index == 0) {
852                     leadingWhitespaces = true;
853                 } else if (!whitespacePatch && !leadingWhitespaces) {
854                     // We only count patches BETWEEN words, not before
855                     ++whitespacePatches;
856                 }
857                 whitespacePatch = !leadingWhitespaces;
858             } else if (cluster->isIdeographic()) {
859                 // Whitespace break before and after
860                 if (!whitespacePatch && index != 0) {
861                     // We only count patches BETWEEN words, not before
862                     ++whitespacePatches; // before
863                 }
864                 whitespacePatch = true;
865                 leadingWhitespaces = false;
866                 ++whitespacePatches;    // after
867             } else {
868                 whitespacePatch = false;
869                 leadingWhitespaces = false;
870             }
871             textLen += cluster->width();
872             return true;
873         });
874 
875     if (whitespacePatch) {
876         // We only count patches BETWEEN words, not after
877         --whitespacePatches;
878     }
879     if (whitespacePatches == 0) {
880         if (fOwner->paragraphStyle().getTextDirection() == TextDirection::kRtl) {
881             // Justify -> Right align
882             fShift = maxWidth - textLen;
883         }
884         return;
885     }
886     SkScalar step = (maxWidth - textLen) / whitespacePatches;
887     SkScalar shift = 0.0f;
888     SkScalar prevShift = 0.0f;
889 
890     // Deal with the ghost spaces
891     auto ghostShift = maxWidth - this->fAdvance.fX;
892     // Spread the extra whitespaces
893     whitespacePatch = false;
894     // Do not break on leading whitespaces
895     leadingWhitespaces = false;
896     this->iterateThroughClustersInGlyphsOrder(false, true, [&](const Cluster* cluster, ClusterIndex index, bool ghost) {
897 
898         if (ghost) {
899             if (cluster->run().leftToRight()) {
900                 this->shiftCluster(cluster, ghostShift, ghostShift);
901             }
902             return true;
903         }
904 
905         if (cluster->isWhitespaceBreak()) {
906             if (index == 0) {
907                 leadingWhitespaces = true;
908             } else if (!whitespacePatch && !leadingWhitespaces) {
909                 shift += step;
910                 whitespacePatch = true;
911                 --whitespacePatches;
912             }
913         } else if (cluster->isIdeographic()) {
914             if (!whitespacePatch && index != 0) {
915                 shift += step;
916                --whitespacePatches;
917             }
918             whitespacePatch = false;
919             leadingWhitespaces = false;
920         } else {
921             whitespacePatch = false;
922             leadingWhitespaces = false;
923         }
924         this->shiftCluster(cluster, shift, prevShift);
925         prevShift = shift;
926         // We skip ideographic whitespaces
927         if (!cluster->isWhitespaceBreak() && cluster->isIdeographic()) {
928             shift += step;
929             whitespacePatch = true;
930             --whitespacePatches;
931         }
932         return true;
933     });
934 
935     if (whitespacePatch && whitespacePatches < 0) {
936         whitespacePatches++;
937         shift -= step;
938     }
939 
940     SkAssertResult(nearlyEqual(shift, maxWidth - textLen));
941     SkASSERT(whitespacePatches == 0);
942 
943     this->fWidthWithSpaces += ghostShift;
944     this->fAdvance.fX = maxWidth;
945 }
946 #endif
947 
948 void TextLine::shiftCluster(const Cluster* cluster, SkScalar shift, SkScalar prevShift) {
949 
950     auto& run = cluster->run();
951     auto start = cluster->startPos();
952     auto end = cluster->endPos();
953 
954     if (end == run.size()) {
955         // Set the same shift for the fake last glyph (to avoid all extra checks)
956         ++end;
957     }
958 
959     if (run.fJustificationShifts.empty()) {
960         // Do not fill this array until needed
961         run.fJustificationShifts.push_back_n(run.size() + 1, { 0, 0 });
962     }
963 
964     for (size_t pos = start; pos < end; ++pos) {
965         run.fJustificationShifts[pos] = { shift, prevShift };
966     }
967 }
968 
969 void TextLine::spacingCluster(const Cluster* cluster, SkScalar spacing, SkScalar prevSpacing) {
970     auto& run = cluster->run();
971     auto start = cluster->startPos();
972     auto end = cluster->endPos();
973     if (end == run.size()) {
974         // Set the same shift for the fake last glyph (to avoid all extra checks)
975         ++end;
976     }
977 
978     if (run.fAutoSpacings.empty()) {
979         // Do not fill this array until needed
980         run.fAutoSpacings.push_back_n(run.size() + 1, { 0, 0 });
981     }
982 
983     for (size_t pos = start; pos < end; ++pos) {
984         run.fAutoSpacings[pos] = { spacing, prevSpacing};
985     }
986 }
987 
988 void TextLine::countWord(int& wordCount, bool& inWord) {
989     for (auto clusterIndex = fGhostClusterRange.start; clusterIndex < fGhostClusterRange.end; ++clusterIndex) {
990         auto& cluster = fOwner->cluster(clusterIndex);
991         if (cluster.isWordBreak()) {
992             inWord = false;
993         } else if (!inWord) {
994             ++wordCount;
995             inWord = true;
996         }
997     }
998 }
999 
1000 void TextLine::ellipsisNotFitProcess(EllipsisModal ellipsisModal) {
1001     if (fEllipsis) {
1002         return;
1003     }
1004 
1005     // Weird situation: ellipsis does not fit; no ellipsis then
1006     switch (ellipsisModal) {
1007         case EllipsisModal::TAIL:
1008             fClusterRange.end = fClusterRange.start;
1009             fGhostClusterRange.end = fClusterRange.start;
1010             fText.end = fText.start;
1011             fTextIncludingNewlines.end = fTextIncludingNewlines.start;
1012             fTextExcludingSpaces.end = fTextExcludingSpaces.start;
1013             fAdvance.fX = 0;
1014             break;
1015         case EllipsisModal::HEAD:
1016             fClusterRange.start = fClusterRange.end;
1017             fGhostClusterRange.start = fClusterRange.end;
1018             fText.start = fText.end;
1019             fTextIncludingNewlines.start = fTextIncludingNewlines.end;
1020             fTextExcludingSpaces.start = fTextExcludingSpaces.end;
1021             fAdvance.fX = 0;
1022             break;
1023         default:
1024             return;
1025     }
1026 }
1027 
1028 #ifdef OHOS_SUPPORT
1029 void TextLine::createTailEllipsis(SkScalar maxWidth, const SkString& ellipsis, bool ltr, WordBreakType wordBreakType) {
1030     // Replace some clusters with the ellipsis
1031     // Go through the clusters in the reverse logical order
1032     // taking off cluster by cluster until the ellipsis fits
1033     SkScalar width = fAdvance.fX;
1034     RunIndex lastRun = EMPTY_RUN;
1035     std::unique_ptr<Run> ellipsisRun;
1036     int wordCount = 0;
1037     bool inWord = false;
1038 
1039     countWord(wordCount, inWord);
1040 
1041     if (fClusterRange.width() == 0 && fGhostClusterRange.width() > 0) {
1042         // Only be entered when line is empty.
1043         handleTailEllipsisInEmptyLine(ellipsisRun, ellipsis, width, wordBreakType);
1044         return;
1045     }
1046 
1047     bool iterForWord = false;
1048     for (auto clusterIndex = fClusterRange.end; clusterIndex > fClusterRange.start; --clusterIndex) {
1049         auto& cluster = fOwner->cluster(clusterIndex - 1);
1050         // Shape the ellipsis if the run has changed
1051         if (lastRun != cluster.runIndex()) {
1052             ellipsisRun = this->shapeEllipsis(ellipsis, &cluster);
1053             // We may need to continue
1054             lastRun = cluster.runIndex();
1055         }
1056 
1057         if (!cluster.isWordBreak()) {
1058             inWord = true;
1059         } else if (inWord) {
1060             --wordCount;
1061             inWord = false;
1062         }
1063         // See if it fits
1064         if (ellipsisRun != nullptr && width + ellipsisRun->advance().fX > maxWidth) {
1065             if (!cluster.isHardBreak()) {
1066                 width -= cluster.width();
1067             }
1068             // Continue if the ellipsis does not fit
1069             iterForWord = (wordCount != 1 && wordBreakType != WordBreakType::BREAK_ALL && !cluster.isWordBreak());
1070             if (std::floor(width) > 0) {
1071                 continue;
1072             }
1073         }
1074 
1075         if (iterForWord && !cluster.isWordBreak()) {
1076             width -= cluster.width();
1077             if (std::floor(width) > 0) {
1078                 continue;
1079             }
1080         }
1081 
1082         fEllipsis = std::move(ellipsisRun);
1083         fEllipsis->fTextRange = TextRange(cluster.textRange().end, cluster.textRange().end + ellipsis.size());
1084         TailEllipsisUpdateLine(cluster, width, clusterIndex, wordBreakType);
1085 
1086         break;
1087     }
1088 
1089     fWidthWithSpaces = width;
1090 
1091     ellipsisNotFitProcess(EllipsisModal::TAIL);
1092 }
1093 
1094 void TextLine::handleTailEllipsisInEmptyLine(std::unique_ptr<Run>& ellipsisRun, const SkString& ellipsis,
1095     SkScalar width, WordBreakType wordBreakType)
1096 {
1097     auto& cluster = fOwner->cluster(fClusterRange.start);
1098     ellipsisRun = this->shapeEllipsis(ellipsis, &cluster);
1099     fEllipsis = std::move(ellipsisRun);
1100     fEllipsis->fTextRange = TextRange(cluster.textRange().end, cluster.textRange().end + ellipsis.size());
1101     TailEllipsisUpdateLine(cluster, width, fGhostClusterRange.end, wordBreakType);
1102     fWidthWithSpaces = width;
1103     ellipsisNotFitProcess(EllipsisModal::TAIL);
1104 }
1105 
1106 void TextLine::TailEllipsisUpdateLine(Cluster& cluster, float width, size_t clusterIndex, WordBreakType wordBreakType)
1107 {
1108     // We found enough room for the ellipsis
1109     fAdvance.fX = width;
1110     fEllipsis->setOwner(fOwner);
1111     fEllipsis->fClusterStart = cluster.textRange().end;
1112 
1113     // Let's update the line
1114     if (wordBreakType != WordBreakType::BREAK_HYPHEN) {
1115         fTextRangeReplacedByEllipsis = TextRange(cluster.textRange().end, fOwner->text().size());
1116     }
1117     fClusterRange.end = clusterIndex;
1118     fGhostClusterRange.end = fClusterRange.end;
1119     // Get the last run directions after clipping
1120     fEllipsisIndex = cluster.runIndex();
1121     fLastClipRunLtr = fOwner->run(fEllipsisIndex).leftToRight();
1122     fText.end = cluster.textRange().end;
1123     fTextIncludingNewlines.end = cluster.textRange().end;
1124     fTextExcludingSpaces.end = cluster.textRange().end;
1125 
1126     if (SkScalarNearlyZero(width)) {
1127         fRunsInVisualOrder.reset();
1128     }
1129 }
1130 #endif
1131 
1132 #ifdef OHOS_SUPPORT
1133 void TextLine::createHeadEllipsis(SkScalar maxWidth, const SkString& ellipsis, bool) {
1134     if (fAdvance.fX <= maxWidth) {
1135         return;
1136     }
1137     SkScalar width = fAdvance.fX;
1138     std::unique_ptr<Run> ellipsisRun;
1139     RunIndex lastRun = EMPTY_RUN;
1140     for (auto clusterIndex = fGhostClusterRange.start; clusterIndex < fGhostClusterRange.end; ++clusterIndex) {
1141         auto& cluster = fOwner->cluster(clusterIndex);
1142         // Shape the ellipsis if the run has changed
1143         if (lastRun != cluster.runIndex()) {
1144             ellipsisRun = this->shapeEllipsis(ellipsis, &cluster);
1145             // We may need to continue
1146             lastRun = cluster.runIndex();
1147         }
1148         // See if it fits
1149         if (ellipsisRun && width + ellipsisRun->advance().fX > maxWidth) {
1150             width -= cluster.width();
1151             // Continue if the ellipsis does not fit
1152             if (std::floor(width) > 0) {
1153                 continue;
1154             }
1155         }
1156 
1157         // Get the last run directions after clipping
1158         fEllipsisIndex = cluster.runIndex();
1159         fLastClipRunLtr = fOwner->run(fEllipsisIndex).leftToRight();
1160 
1161         // We found enough room for the ellipsis
1162         fAdvance.fX = width + ellipsisRun->advance().fX;
1163         fEllipsis = std::move(ellipsisRun);
1164         fEllipsis->setOwner(fOwner);
1165         fTextRangeReplacedByEllipsis = TextRange(0, cluster.textRange().start);
1166         fClusterRange.start = clusterIndex;
1167         fGhostClusterRange.start = fClusterRange.start;
1168         fEllipsis->fClusterStart = 0;
1169         fText.start = cluster.textRange().start;
1170         fTextIncludingNewlines.start = cluster.textRange().start;
1171         fTextExcludingSpaces.start = cluster.textRange().start;
1172         break;
1173     }
1174 
1175     fWidthWithSpaces = width;
1176 
1177     ellipsisNotFitProcess(EllipsisModal::HEAD);
1178 }
1179 #endif
1180 
1181 static inline SkUnichar nextUtf8Unit(const char** ptr, const char* end) {
1182     SkUnichar val = SkUTF::NextUTF8(ptr, end);
1183     return val < 0 ? 0xFFFD : val;
1184 }
1185 
1186 std::unique_ptr<Run> TextLine::shapeEllipsis(const SkString& ellipsis, const Cluster* cluster) {
1187 #ifdef OHOS_SUPPORT
1188     fEllipsisString = ellipsis;
1189 #endif
1190     return shapeString(ellipsis, cluster);
1191 }
1192 
1193 std::unique_ptr<Run> TextLine::shapeString(const SkString& str, const Cluster* cluster) {
1194     class ShapeHandler final : public SkShaper::RunHandler {
1195     public:
1196         ShapeHandler(SkScalar lineHeight, bool useHalfLeading, SkScalar baselineShift, const SkString& str)
1197             : fRun(nullptr), fLineHeight(lineHeight), fUseHalfLeading(useHalfLeading), fBaselineShift(baselineShift), fStr(str) {}
1198         std::unique_ptr<Run> run() & { return std::move(fRun); }
1199 
1200     private:
1201         void beginLine() override {}
1202 
1203         void runInfo(const RunInfo&) override {}
1204 
1205         void commitRunInfo() override {}
1206 
1207         Buffer runBuffer(const RunInfo& info) override {
1208             SkASSERT(!fRun);
1209             fRun = std::make_unique<Run>(nullptr, info, 0, fLineHeight, fUseHalfLeading, fBaselineShift, 0, 0);
1210             return fRun->newRunBuffer();
1211         }
1212 
1213         void commitRunBuffer(const RunInfo& info) override {
1214             fRun->fAdvance.fX = info.fAdvance.fX;
1215             fRun->fAdvance.fY = fRun->advance().fY;
1216             fRun->fPlaceholderIndex = std::numeric_limits<size_t>::max();
1217             // this may not be fully accurate, but limiting the changes to the text line
1218             fRun->fEllipsis = true;
1219         }
1220 
1221         void commitLine() override {}
1222 
1223         std::unique_ptr<Run> fRun;
1224         SkScalar fLineHeight;
1225         bool fUseHalfLeading;
1226         SkScalar fBaselineShift;
1227         SkString fStr;
1228     };
1229 
1230     const Run& run = cluster->run();
1231     TextStyle textStyle = fOwner->paragraphStyle().getTextStyle();
1232     for (auto i = fBlockRange.start; i < fBlockRange.end; ++i) {
1233         auto& block = fOwner->block(i);
1234         if (run.leftToRight() && cluster->textRange().end <= block.fRange.end) {
1235             textStyle = block.fStyle;
1236             break;
1237         } else if (!run.leftToRight() && cluster->textRange().start <= block.fRange.end) {
1238             textStyle = block.fStyle;
1239             break;
1240         }
1241     }
1242 
1243 #ifndef USE_SKIA_TXT
1244     auto shaped = [&](sk_sp<SkTypeface> typeface, bool fallback) -> std::unique_ptr<Run> {
1245 #else
1246     auto shaped = [&](std::shared_ptr<RSTypeface> typeface, bool fallback) -> std::unique_ptr<Run> {
1247 #endif
1248         ShapeHandler handler(run.heightMultiplier(), run.useHalfLeading(), run.baselineShift(), str);
1249 #ifndef USE_SKIA_TXT
1250         SkFont font(typeface, textStyle.getFontSize());
1251         font.setEdging(SkFont::Edging::kAntiAlias);
1252         font.setHinting(SkFontHinting::kSlight);
1253         font.setSubpixel(true);
1254 #else
1255         RSFont font(typeface, textStyle.getFontSize(), 1, 0);
1256         font.SetEdging(RSDrawing::FontEdging::ANTI_ALIAS);
1257         font.SetHinting(RSDrawing::FontHinting::SLIGHT);
1258         font.SetSubpixel(true);
1259 #endif
1260 
1261 #ifndef USE_SKIA_TXT
1262         std::unique_ptr<SkShaper> shaper = SkShaper::MakeShapeDontWrapOrReorder(
1263                             fOwner->getUnicode()->copy(),
1264                             fallback ? SkFontMgr::RefDefault() : SkFontMgr::RefEmpty());
1265 #else
1266         std::unique_ptr<SkShaper> shaper = SkShaper::MakeShapeDontWrapOrReorder(
1267                             fOwner->getUnicode()->copy(),
1268                             fallback ? RSFontMgr::CreateDefaultFontMgr() : RSFontMgr::CreateDefaultFontMgr());
1269 #endif
1270         shaper->shape(str.c_str(),
1271                       str.size(),
1272                       font,
1273                       true,
1274                       std::numeric_limits<SkScalar>::max(),
1275                       &handler);
1276         auto run = handler.run();
1277         run->fTextRange = TextRange(0, str.size());
1278         run->fOwner = fOwner;
1279         return run;
1280     };
1281 
1282     // Check all allowed fonts
1283     auto typefaces = fOwner->fontCollection()->findTypefaces(
1284             textStyle.getFontFamilies(), textStyle.getFontStyle(), textStyle.getFontArguments());
1285     for (const auto& typeface : typefaces) {
1286         auto run = shaped(typeface, false);
1287         if (run->isResolved()) {
1288             return run;
1289         }
1290     }
1291 
1292     // Try the fallback
1293     if (fOwner->fontCollection()->fontFallbackEnabled()) {
1294         const char* ch = str.c_str();
1295         SkUnichar unicode = nextUtf8Unit(&ch, str.c_str() + str.size());
1296 
1297         auto typeface = fOwner->fontCollection()->defaultFallback(
1298             unicode, textStyle.getFontStyle(), textStyle.getLocale());
1299         if (typeface) {
1300             if (textStyle.getFontArguments()) {
1301                 typeface = fOwner->fontCollection()->CloneTypeface(typeface, textStyle.getFontArguments());
1302             }
1303             auto run = shaped(typeface, true);
1304             if (run->isResolved()) {
1305                 return run;
1306             }
1307         }
1308     }
1309 
1310     // Check the current font
1311 #ifndef USE_SKIA_TXT
1312     auto finalRun = shaped(run.fFont.refTypeface(), false);
1313 #else
1314     auto finalRun = shaped(const_cast<RSFont&>(run.fFont).GetTypeface(), false);
1315 #endif
1316     if (finalRun->isResolved()) {
1317         return finalRun;
1318     }
1319     return finalRun;
1320 }
1321 
1322 #ifdef OHOS_SUPPORT
1323 void TextLine::measureTextWithSpacesAtTheEnd(ClipContext& context, bool includeGhostSpaces) const
1324 {
1325     if (compareRound(context.clip.fRight, fAdvance.fX, fOwner->getApplyRoundingHack()) > 0 && !includeGhostSpaces &&
1326         fAdvance.fX > 0) {
1327         // There are few cases when we need it.
1328         // The most important one: we measure the text with spaces at the end (or at the beginning in RTL)
1329         // and we should ignore these spaces
1330         if (fOwner->paragraphStyle().getTextDirection() == TextDirection::kLtr) {
1331             // We only use this member for LTR
1332             context.fExcludedTrailingSpaces = std::max(context.clip.fRight - fAdvance.fX, 0.0f);
1333             context.clippingNeeded = true;
1334             context.clip.fRight = fAdvance.fX;
1335         }
1336     }
1337 }
1338 #endif
1339 
1340 TextLine::ClipContext TextLine::measureTextInsideOneRun(TextRange textRange,
1341                                                         const Run* run,
1342                                                         SkScalar runOffsetInLine,
1343                                                         SkScalar textOffsetInRunInLine,
1344                                                         bool includeGhostSpaces,
1345                                                         TextAdjustment textAdjustment) const {
1346     ClipContext result = { run, 0, run->size(), 0, SkRect::MakeEmpty(), 0, false };
1347 
1348     if (run->fEllipsis) {
1349         // Both ellipsis and placeholders can only be measured as one glyph
1350         result.fTextShift = runOffsetInLine;
1351         result.clip = SkRect::MakeXYWH(runOffsetInLine,
1352                                        sizes().runTop(run, this->fAscentStyle),
1353                                        run->advance().fX,
1354                                        run->calculateHeight(this->fAscentStyle,this->fDescentStyle));
1355         return result;
1356     } else if (run->isPlaceholder()) {
1357         result.fTextShift = runOffsetInLine;
1358         if (SkScalarIsFinite(run->fFontMetrics.fAscent)) {
1359           result.clip = SkRect::MakeXYWH(runOffsetInLine,
1360                                          sizes().runTop(run, this->fAscentStyle),
1361                                          run->advance().fX,
1362                                          run->calculateHeight(this->fAscentStyle,this->fDescentStyle));
1363         } else {
1364             result.clip = SkRect::MakeXYWH(runOffsetInLine, run->fFontMetrics.fAscent, run->advance().fX, 0);
1365         }
1366         return result;
1367     } else if (textRange.empty()) {
1368         return result;
1369     }
1370 
1371     TextRange originalTextRange(textRange); // We need it for proportional measurement
1372     // Find [start:end] clusters for the text
1373     while (true) {
1374         // Update textRange by cluster edges (shift start up to the edge of the cluster)
1375         // TODO: remove this limitation?
1376         TextRange updatedTextRange;
1377         bool found;
1378         std::tie(found, updatedTextRange.start, updatedTextRange.end) =
1379                                         run->findLimitingGlyphClusters(textRange);
1380         if (!found) {
1381             return result;
1382         }
1383 
1384         if ((textAdjustment & TextAdjustment::Grapheme) == 0) {
1385             textRange = updatedTextRange;
1386             break;
1387         }
1388 
1389         // Update text range by grapheme edges (shift start up to the edge of the grapheme)
1390         std::tie(found, updatedTextRange.start, updatedTextRange.end) =
1391                                     run->findLimitingGraphemes(updatedTextRange);
1392         if (updatedTextRange == textRange) {
1393             break;
1394         }
1395 
1396         // Some clusters are inside graphemes and we need to adjust them
1397         //SkDebugf("Correct range: [%d:%d) -> [%d:%d)\n", textRange.start, textRange.end, startIndex, endIndex);
1398         textRange = updatedTextRange;
1399 
1400         // Move the start until it's on the grapheme edge (and glypheme, too)
1401     }
1402     Cluster* start = &fOwner->cluster(fOwner->clusterIndex(textRange.start));
1403     Cluster* end = &fOwner->cluster(fOwner->clusterIndex(textRange.end - (textRange.width() == 0 ? 0 : 1)));
1404 
1405     if (!run->leftToRight()) {
1406         std::swap(start, end);
1407     }
1408     result.pos = start->startPos();
1409     result.size = (end->isHardBreak() ? end->startPos() : end->endPos()) - start->startPos();
1410     auto textStartInRun = run->positionX(start->startPos());
1411     auto textStartInLine = runOffsetInLine + textOffsetInRunInLine;
1412     if (!run->leftToRight()) {
1413         std::swap(start, end);
1414     }
1415 /*
1416     if (!run->fJustificationShifts.empty()) {
1417         SkDebugf("Justification for [%d:%d)\n", textRange.start, textRange.end);
1418         for (auto i = result.pos; i < result.pos + result.size; ++i) {
1419             auto j = run->fJustificationShifts[i];
1420             SkDebugf("[%d] = %f %f\n", i, j.fX, j.fY);
1421         }
1422     }
1423 */
1424     // Calculate the clipping rectangle for the text with cluster edges
1425     // There are 2 cases:
1426     // EOL (when we expect the last cluster clipped without any spaces)
1427     // Anything else (when we want the cluster width contain all the spaces -
1428     // coming from letter spacing or word spacing or justification)
1429     result.clip =
1430             SkRect::MakeXYWH(0,
1431                              sizes().runTop(run, this->fAscentStyle),
1432                              run->calculateWidth(result.pos, result.pos + result.size, false),
1433                              run->calculateHeight(this->fAscentStyle,this->fDescentStyle));
1434 
1435     // Correct the width in case the text edges don't match clusters
1436     // TODO: This is where we get smart about selecting a part of a cluster
1437     //  by shaping each grapheme separately and then use the result sizes
1438     //  to calculate the proportions
1439     auto leftCorrection = start->sizeToChar(originalTextRange.start);
1440     auto rightCorrection = end->sizeFromChar(originalTextRange.end - 1);
1441     /*
1442     SkDebugf("[%d: %d) => [%d: %d), @%d, %d: [%f:%f) + [%f:%f) = ", // جَآَهُ
1443              originalTextRange.start, originalTextRange.end, textRange.start, textRange.end,
1444              result.pos, result.size,
1445              result.clip.fLeft, result.clip.fRight, leftCorrection, rightCorrection);
1446      */
1447     result.clippingNeeded = leftCorrection != 0 || rightCorrection != 0;
1448     if (run->leftToRight()) {
1449         result.clip.fLeft += leftCorrection;
1450         result.clip.fRight -= rightCorrection;
1451         textStartInLine -= leftCorrection;
1452     } else {
1453         result.clip.fRight -= leftCorrection;
1454         result.clip.fLeft += rightCorrection;
1455         textStartInLine -= rightCorrection;
1456     }
1457 
1458     result.clip.offset(textStartInLine, 0);
1459     //SkDebugf("@%f[%f:%f)\n", textStartInLine, result.clip.fLeft, result.clip.fRight);
1460 
1461 #ifdef OHOS_SUPPORT
1462     measureTextWithSpacesAtTheEnd(result, includeGhostSpaces);
1463 #else
1464     if (compareRound(result.clip.fRight, fAdvance.fX, fOwner->getApplyRoundingHack()) > 0 && !includeGhostSpaces) {
1465         // There are few cases when we need it.
1466         // The most important one: we measure the text with spaces at the end (or at the beginning in RTL)
1467         // and we should ignore these spaces
1468         if (fOwner->paragraphStyle().getTextDirection() == TextDirection::kLtr) {
1469             // We only use this member for LTR
1470             result.fExcludedTrailingSpaces = std::max(result.clip.fRight - fAdvance.fX, 0.0f);
1471             result.clippingNeeded = true;
1472             result.clip.fRight = fAdvance.fX;
1473         }
1474     }
1475 
1476     if (result.clip.width() < 0) {
1477         // Weird situation when glyph offsets move the glyph to the left
1478         // (happens with zalgo texts, for instance)
1479         result.clip.fRight = result.clip.fLeft;
1480     }
1481 #endif
1482 
1483     // The text must be aligned with the lineOffset
1484     result.fTextShift = textStartInLine - textStartInRun;
1485 
1486     return result;
1487 }
1488 
1489 void TextLine::iterateThroughClustersInGlyphsOrder(bool reversed,
1490                                                    bool includeGhosts,
1491                                                    const ClustersVisitor& visitor) const {
1492     // Walk through the clusters in the logical order (or reverse)
1493     SkSpan<const size_t> runs(fRunsInVisualOrder.data(), fRunsInVisualOrder.size());
1494     bool ignore = false;
1495     ClusterIndex index = 0;
1496     directional_for_each(runs, !reversed, [&](decltype(runs[0]) r) {
1497         if (ignore) return;
1498         auto run = this->fOwner->run(r);
1499         auto trimmedRange = fClusterRange.intersection(run.clusterRange());
1500         auto trailedRange = fGhostClusterRange.intersection(run.clusterRange());
1501         SkASSERT(trimmedRange.start == trailedRange.start);
1502 
1503         auto trailed = fOwner->clusters(trailedRange);
1504         auto trimmed = fOwner->clusters(trimmedRange);
1505         directional_for_each(trailed, reversed != run.leftToRight(), [&](Cluster& cluster) {
1506             if (ignore) return;
1507             bool ghost =  &cluster >= trimmed.end();
1508             if (!includeGhosts && ghost) {
1509                 return;
1510             }
1511             if (!visitor(&cluster, index++, ghost)) {
1512 
1513                 ignore = true;
1514                 return;
1515             }
1516         });
1517     });
1518 }
1519 
1520 #ifdef OHOS_SUPPORT
1521 void TextLine::computeNextPaintGlyphRange(ClipContext& context,
1522     const TextRange& lastGlyphRange, StyleType styleType) const
1523 {
1524     if (styleType != StyleType::kForeground) {
1525         return;
1526     }
1527     TextRange curGlyphRange = TextRange(context.pos, context.pos + context.size);
1528     auto intersect = intersected(lastGlyphRange, curGlyphRange);
1529     if (intersect == EMPTY_TEXT || (intersect.start != curGlyphRange.start && intersect.end != curGlyphRange.end)) {
1530         return;
1531     }
1532     if (intersect.start == curGlyphRange.start) {
1533         curGlyphRange = TextRange(intersect.end, curGlyphRange.end);
1534     } else if (intersect.end == curGlyphRange.end) {
1535         curGlyphRange = TextRange(curGlyphRange.start, intersect.start);
1536     }
1537 
1538     context.pos = curGlyphRange.start;
1539     context.size = curGlyphRange.width();
1540 }
1541 #endif
1542 
1543 SkScalar TextLine::iterateThroughSingleRunByStyles(TextAdjustment textAdjustment,
1544                                                    const Run* run,
1545                                                    SkScalar runOffset,
1546                                                    TextRange textRange,
1547                                                    StyleType styleType,
1548                                                    const RunStyleVisitor& visitor) const {
1549     auto includeGhostSpaces = (styleType == StyleType::kDecorations || styleType == StyleType::kBackground ||
1550         styleType == StyleType::kNone);
1551     auto correctContext = [&](TextRange textRange, SkScalar textOffsetInRun) -> ClipContext {
1552         auto result = this->measureTextInsideOneRun(
1553             textRange, run, runOffset, textOffsetInRun, includeGhostSpaces, textAdjustment);
1554         if (styleType == StyleType::kDecorations) {
1555             // Decorations are drawn based on the real font metrics (regardless of styles and strut)
1556             result.clip.fTop = this->sizes().runTop(run, LineMetricStyle::CSS) - run->baselineShift();
1557             result.clip.fBottom = result.clip.fTop +
1558                                   run->calculateHeight(LineMetricStyle::CSS, LineMetricStyle::CSS);
1559         }
1560         return result;
1561     };
1562 
1563     if (run->fEllipsis) {
1564         // Extra efforts to get the ellipsis text style
1565         ClipContext clipContext = correctContext(run->textRange(), 0.0f);
1566         for (BlockIndex index = fBlockRange.start; index < fBlockRange.end; ++index) {
1567             auto block = fOwner->styles().begin() + index;
1568 #ifdef OHOS_SUPPORT
1569             TextRange intersect = intersected(block->fRange,
1570                 TextRange(run->textRange().start - 1, run->textRange().end));
1571             if (intersect.width() > 0) {
1572                 visitor(fTextRangeReplacedByEllipsis, block->fStyle, clipContext);
1573                 return run->advance().fX;
1574             }
1575 #else
1576            if (block->fRange.start >= run->fClusterStart && block->fRange.end < run->fClusterStart) {
1577                visitor(fTextRangeReplacedByEllipsis, block->fStyle, clipContext);
1578                return run->advance().fX;
1579            }
1580 #endif
1581         }
1582         SkASSERT(false);
1583     }
1584 
1585     if (styleType == StyleType::kNone) {
1586         ClipContext clipContext = correctContext(textRange, 0.0f);
1587 #ifdef OHOS_SUPPORT
1588         if (clipContext.clip.height() > 0 ||
1589             (run->isPlaceholder() && SkScalarNearlyZero(clipContext.clip.height()))) {
1590 #else
1591         if (clipContext.clip.height() > 0) {
1592 #endif
1593             visitor(textRange, TextStyle(), clipContext);
1594             return clipContext.clip.width();
1595         } else {
1596             return 0;
1597         }
1598     }
1599 
1600     TextIndex start = EMPTY_INDEX;
1601     size_t size = 0;
1602     const TextStyle* prevStyle = nullptr;
1603     SkScalar textOffsetInRun = 0;
1604 #ifdef OHOS_SUPPORT
1605     TextRange lastGlyphRange = EMPTY_TEXT;
1606 #endif
1607     const BlockIndex blockRangeSize = fBlockRange.end - fBlockRange.start;
1608     for (BlockIndex index = 0; index <= blockRangeSize; ++index) {
1609 
1610         TextRange intersect;
1611         TextStyle* style = nullptr;
1612         if (index < blockRangeSize) {
1613             auto block = fOwner->styles().begin() +
1614                  (run->leftToRight() ? fBlockRange.start + index : fBlockRange.end - index - 1);
1615 
1616             // Get the text
1617             intersect = intersected(block->fRange, textRange);
1618             if (intersect.width() == 0) {
1619                 if (start == EMPTY_INDEX) {
1620                     // This style is not applicable to the text yet
1621                     continue;
1622                 } else {
1623                     // We have found all the good styles already
1624                     // but we need to process the last one of them
1625                     intersect = TextRange(start, start + size);
1626                     index = fBlockRange.end;
1627                 }
1628             } else {
1629                 // Get the style
1630                 style = &block->fStyle;
1631                 if (start != EMPTY_INDEX && style->matchOneAttribute(styleType, *prevStyle)) {
1632                     size += intersect.width();
1633                     // RTL text intervals move backward
1634                     start = std::min(intersect.start, start);
1635                     continue;
1636                 } else if (start == EMPTY_INDEX ) {
1637                     // First time only
1638                     prevStyle = style;
1639                     size = intersect.width();
1640                     start = intersect.start;
1641                     continue;
1642                 }
1643             }
1644         } else if (prevStyle != nullptr) {
1645             // This is the last style
1646         } else {
1647             break;
1648         }
1649 
1650         // We have the style and the text
1651         auto runStyleTextRange = TextRange(start, start + size);
1652         ClipContext clipContext = correctContext(runStyleTextRange, textOffsetInRun);
1653         textOffsetInRun += clipContext.clip.width();
1654         if (clipContext.clip.height() == 0) {
1655             continue;
1656         }
1657 
1658         RectStyle temp;
1659         if (styleType == StyleType::kBackground &&
1660             prevStyle->getBackgroundRect() != temp &&
1661             prevStyle->getHeight() != 0) {
1662 #ifdef OHOS_SUPPORT
1663                 clipContext.clip.fTop = run->fFontMetrics.fAscent + this->baseline() + run->fBaselineShift;
1664 #else
1665                 clipContext.clip.fTop = run->fFontMetrics.fAscent - run->fCorrectAscent;
1666 #endif
1667                 clipContext.clip.fBottom = clipContext.clip.fTop + run->fFontMetrics.fDescent -
1668                     run->fFontMetrics.fAscent;
1669         }
1670 #ifdef OHOS_SUPPORT
1671         computeNextPaintGlyphRange(clipContext, lastGlyphRange, styleType);
1672         if (clipContext.size != 0) {
1673             lastGlyphRange = TextRange(clipContext.pos, clipContext.pos + clipContext.size);
1674         }
1675 #endif
1676         visitor(runStyleTextRange, *prevStyle, clipContext);
1677 
1678         // Start all over again
1679         prevStyle = style;
1680         start = intersect.start;
1681         size = intersect.width();
1682     }
1683     return textOffsetInRun;
1684 }
1685 
1686 #ifdef OHOS_SUPPORT
1687 bool TextLine::processEllipsisRun(bool& isAlreadyUseEllipsis,
1688                                   SkScalar& runOffset,
1689                                   EllipsisReadStrategy ellipsisReadStrategy,
1690                                   const RunVisitor& visitor,
1691                                   SkScalar& runWidthInLine) const {
1692     isAlreadyUseEllipsis = true;
1693     return processInsertedRun(fEllipsis.get(), runOffset, ellipsisReadStrategy,
1694                               visitor, runWidthInLine);
1695 }
1696 
1697 bool TextLine::processInsertedRun(const Run* extra,
1698                                   SkScalar& runOffset,
1699                                   EllipsisReadStrategy ellipsisReadStrategy,
1700                                   const RunVisitor& visitor,
1701                                   SkScalar& runWidthInLine) const {
1702     runOffset += extra->offset().fX;
1703     if (ellipsisReadStrategy == EllipsisReadStrategy::READ_REPLACED_WORD) {
1704         if (!visitor(extra, runOffset, fTextRangeReplacedByEllipsis, &runWidthInLine)) {
1705             LOGE("Visitor process ellipsis replace word error!");
1706             return false;
1707         }
1708     } else if (ellipsisReadStrategy == EllipsisReadStrategy::READ_ELLIPSIS_WORD) {
1709         if (!visitor(extra, runOffset, extra->textRange(), &runWidthInLine)) {
1710             LOGE("Visitor process ellipsis word error!");
1711             return false;
1712         }
1713     } else {
1714         runWidthInLine = extra->advance().fX;
1715     }
1716     return true;
1717 }
1718 #endif
1719 
1720 #ifdef OHOS_SUPPORT
1721 void TextLine::iterateThroughVisualRuns(EllipsisReadStrategy ellipsisReadStrategy,
1722                                         bool includingGhostSpaces,
1723                                         const RunVisitor& visitor) const {
1724     // Walk through all the runs that intersect with the line in visual order
1725     SkScalar width = 0;
1726     SkScalar runOffset = 0;
1727     SkScalar totalWidth = 0;
1728 #ifdef OHOS_SUPPORT
1729     bool ellipsisModeIsHead = fIsTextLineEllipsisHeadModal ? true :
1730             fOwner->paragraphStyle().getEllipsisMod() == EllipsisModal::HEAD;
1731 #else
1732     bool ellipsisModeIsHead = fOwner->paragraphStyle().getEllipsisMod() == EllipsisModal::HEAD;
1733 #endif
1734     bool isAlreadyUseEllipsis = false;
1735     auto textRange = includingGhostSpaces ? this->textWithNewlines() : this->trimmedText();
1736 
1737     if (fRunsInVisualOrder.size() == 0) {
1738         if (fEllipsis != nullptr) {
1739             if (!processEllipsisRun(isAlreadyUseEllipsis, runOffset, ellipsisReadStrategy, visitor, width)) {
1740                 return;
1741             }
1742             totalWidth += width;
1743         }
1744         if (fHyphenRun != nullptr) { // not sure if this is basically valid in real life
1745             if (!processInsertedRun(fHyphenRun.get(), runOffset, ellipsisReadStrategy, visitor, width)) {
1746                 return;
1747             }
1748             totalWidth += width;
1749         }
1750     }
1751 
1752     for (auto& runIndex : fRunsInVisualOrder) {
1753         // add the lastClipRun's left ellipsis if necessary
1754         if (!isAlreadyUseEllipsis && fEllipsisIndex == runIndex &&
1755             ((!fLastClipRunLtr && !ellipsisModeIsHead) || (ellipsisModeIsHead && fLastClipRunLtr))) {
1756             if (!processEllipsisRun(isAlreadyUseEllipsis, runOffset, ellipsisReadStrategy, visitor, width)) {
1757                 return;
1758             }
1759             runOffset += width;
1760             totalWidth += width;
1761         }
1762 
1763         const auto run = &this->fOwner->run(runIndex);
1764         auto lineIntersection = intersected(run->textRange(), textRange);
1765         if (lineIntersection.width() == 0 && this->width() != 0) {
1766             // TODO: deal with empty runs in a better way
1767             continue;
1768         }
1769         if (!run->leftToRight() && runOffset == 0 && includingGhostSpaces) {
1770             // runOffset does not take in account a possibility
1771             // that RTL run could start before the line (trailing spaces)
1772             // so we need to do runOffset -= "trailing whitespaces length"
1773             TextRange whitespaces = intersected(
1774                     TextRange(fTextExcludingSpaces.end, fTextIncludingNewlines.end), run->fTextRange);
1775             if (whitespaces.width() > 0) {
1776                 auto whitespacesLen = measureTextInsideOneRun(whitespaces, run, runOffset, 0, true,
1777                                                               TextAdjustment::GlyphCluster).clip.width();
1778                 runOffset -= whitespacesLen;
1779             }
1780         }
1781 
1782         if (!visitor(run, runOffset, lineIntersection, &width)) {
1783             return;
1784         }
1785 
1786         runOffset += width;
1787         totalWidth += width;
1788 
1789         // add the lastClipRun's right ellipsis if necessary
1790         if (!isAlreadyUseEllipsis && fEllipsisIndex == runIndex) {
1791             if (!processEllipsisRun(isAlreadyUseEllipsis, runOffset, ellipsisReadStrategy, visitor, width)) {
1792                 return;
1793             }
1794             runOffset += width;
1795             totalWidth += width;
1796         }
1797         if (runIndex == fHyphenIndex) {
1798             if (!processInsertedRun(fHyphenRun.get(), runOffset, ellipsisReadStrategy, visitor, width)) {
1799                 return;
1800             }
1801             runOffset += width;
1802             totalWidth += width;
1803         }
1804     }
1805 
1806     if (!includingGhostSpaces && compareRound(totalWidth, this->width(), fOwner->getApplyRoundingHack()) != 0) {
1807     // This is a very important assert!
1808     // It asserts that 2 different ways of calculation come with the same results
1809         SkDebugf("ASSERT: %f != %f\n", totalWidth, this->width());
1810         SkASSERT(false);
1811     }
1812 }
1813 #else
1814 void TextLine::iterateThroughVisualRuns(bool includingGhostSpaces, const RunVisitor& visitor) const {
1815 
1816     // Walk through all the runs that intersect with the line in visual order
1817     SkScalar width = 0;
1818     SkScalar runOffset = 0;
1819     SkScalar totalWidth = 0;
1820     auto textRange = includingGhostSpaces ? this->textWithNewlines() : this->trimmedText();
1821     for (auto& runIndex : fRunsInVisualOrder) {
1822 
1823         const auto run = &this->fOwner->run(runIndex);
1824         auto lineIntersection = intersected(run->textRange(), textRange);
1825         if (lineIntersection.width() == 0 && this->width() != 0) {
1826             // TODO: deal with empty runs in a better way
1827             continue;
1828         }
1829         if (!run->leftToRight() && runOffset == 0 && includingGhostSpaces) {
1830             // runOffset does not take in account a possibility
1831             // that RTL run could start before the line (trailing spaces)
1832             // so we need to do runOffset -= "trailing whitespaces length"
1833             TextRange whitespaces = intersected(
1834                     TextRange(fTextExcludingSpaces.end, fTextIncludingNewlines.end), run->fTextRange);
1835             if (whitespaces.width() > 0) {
1836                 auto whitespacesLen = measureTextInsideOneRun(whitespaces, run, runOffset, 0, true, false).clip.width();
1837                 runOffset -= whitespacesLen;
1838             }
1839         }
1840         runOffset += width;
1841         totalWidth += width;
1842         if (!visitor(run, runOffset, lineIntersection, &width)) {
1843             return;
1844         }
1845     }
1846 
1847     runOffset += width;
1848     totalWidth += width;
1849 
1850     if (fEllipsis != nullptr) {
1851         if (visitor(fEllipsis.get(), runOffset, fEllipsis->textRange(), &width)) {
1852             totalWidth += width;
1853         }
1854     }
1855 
1856     // This is a very important assert!
1857     // It asserts that 2 different ways of calculation come with the same results
1858     if (!includingGhostSpaces && compareRound(totalWidth, this->width()) != 0) {
1859         SkDebugf("ASSERT: %f != %f\n", totalWidth, this->width());
1860         SkASSERT(false);
1861     }
1862 }
1863 #endif
1864 
1865 SkVector TextLine::offset() const {
1866     return fOffset + SkVector::Make(fShift, 0);
1867 }
1868 
1869 LineMetrics TextLine::getMetrics() const {
1870     LineMetrics result;
1871 
1872     // Fill out the metrics
1873     fOwner->ensureUTF16Mapping();
1874     result.fStartIndex = fOwner->getUTF16Index(fTextExcludingSpaces.start);
1875     result.fEndExcludingWhitespaces = fOwner->getUTF16Index(fTextExcludingSpaces.end);
1876     result.fEndIndex = fOwner->getUTF16Index(fText.end);
1877     result.fEndIncludingNewline = fOwner->getUTF16Index(fTextIncludingNewlines.end);
1878     result.fHardBreak = endsWithHardLineBreak();
1879     result.fAscent = - fMaxRunMetrics.ascent();
1880     result.fDescent = fMaxRunMetrics.descent();
1881     result.fUnscaledAscent = - fMaxRunMetrics.ascent(); // TODO: implement
1882     result.fHeight = fAdvance.fY;
1883     result.fWidth = fAdvance.fX;
1884     if (fOwner->getApplyRoundingHack()) {
1885         result.fHeight = littleRound(result.fHeight);
1886         result.fWidth = littleRound(result.fWidth);
1887     }
1888     result.fLeft = this->offset().fX;
1889     // This is Flutter definition of a baseline
1890     result.fBaseline = this->offset().fY + this->height() - this->sizes().descent();
1891     result.fLineNumber = this - fOwner->lines().begin();
1892     result.fWidthWithSpaces = fWidthWithSpaces;
1893     result.fTopHeight = this->offset().fY;
1894 
1895     // Fill out the style parts
1896 #ifdef OHOS_SUPPORT
1897     this->iterateThroughVisualRuns(EllipsisReadStrategy::READ_REPLACED_WORD, false,
1898 #else
1899     this->iterateThroughVisualRuns(false,
1900 #endif
1901         [this, &result]
1902         (const Run* run, SkScalar runOffsetInLine, TextRange textRange, SkScalar* runWidthInLine) {
1903         if (run->placeholderStyle() != nullptr) {
1904             *runWidthInLine = run->advance().fX;
1905             return true;
1906         }
1907         *runWidthInLine = this->iterateThroughSingleRunByStyles(
1908         TextAdjustment::GlyphCluster, run, runOffsetInLine, textRange, StyleType::kForeground,
1909         [&result, &run](TextRange textRange, const TextStyle& style, const ClipContext& context) {
1910 #ifndef USE_SKIA_TXT
1911             SkFontMetrics fontMetrics;
1912             run->fFont.getMetrics(&fontMetrics);
1913 #else
1914             RSFontMetrics fontMetrics;
1915             run->fFont.GetMetrics(&fontMetrics);
1916 #endif
1917 #ifdef OHOS_SUPPORT
1918             auto decompressFont = run->fFont;
1919             scaleFontWithCompressionConfig(decompressFont, ScaleOP::DECOMPRESS);
1920             metricsIncludeFontPadding(&fontMetrics, decompressFont);
1921 #endif
1922             StyleMetrics styleMetrics(&style, fontMetrics);
1923             result.fLineMetrics.emplace(textRange.start, styleMetrics);
1924         });
1925         return true;
1926     });
1927 
1928     return result;
1929 }
1930 
1931 bool TextLine::isFirstLine() const {
1932     return this == &fOwner->lines().front();
1933 }
1934 
1935 bool TextLine::isLastLine() const {
1936     return this == &fOwner->lines().back();
1937 }
1938 
1939 bool TextLine::endsWithHardLineBreak() const {
1940     // TODO: For some reason Flutter imagines a hard line break at the end of the last line.
1941     //  To be removed...
1942     return (fGhostClusterRange.width() > 0 && fOwner->cluster(fGhostClusterRange.end - 1).isHardBreak()) ||
1943            fEllipsis != nullptr ||
1944            fGhostClusterRange.end == fOwner->clusters().size() - 1;
1945 }
1946 #ifdef OHOS_SUPPORT
1947 bool TextLine::endsWithOnlyHardBreak() const
1948 {
1949     return (fGhostClusterRange.width() > 0 && fOwner->cluster(fGhostClusterRange.end - 1).isHardBreak());
1950 }
1951 #endif
1952 
1953 void TextLine::getRectsForRange(TextRange textRange0,
1954                                 RectHeightStyle rectHeightStyle,
1955                                 RectWidthStyle rectWidthStyle,
1956                                 std::vector<TextBox>& boxes) const
1957 {
1958     const Run* lastRun = nullptr;
1959     auto startBox = boxes.size();
1960 #ifdef OHOS_SUPPORT
1961     this->iterateThroughVisualRuns(EllipsisReadStrategy::READ_REPLACED_WORD, true,
1962 #else
1963     this->iterateThroughVisualRuns(true,
1964 #endif
1965         [textRange0, rectHeightStyle, rectWidthStyle, &boxes, &lastRun, startBox, this]
1966         (const Run* run, SkScalar runOffsetInLine, TextRange textRange, SkScalar* runWidthInLine) {
1967         *runWidthInLine = this->iterateThroughSingleRunByStyles(
1968         TextAdjustment::GraphemeGluster, run, runOffsetInLine, textRange, StyleType::kNone,
1969         [run, runOffsetInLine, textRange0, rectHeightStyle, rectWidthStyle, &boxes, &lastRun, startBox, this]
1970         (TextRange textRange, const TextStyle& style, const TextLine::ClipContext& lineContext) {
1971 
1972             auto intersect = textRange * textRange0;
1973 #ifdef OHOS_SUPPORT
1974             if (intersect.empty() && !this->fBreakWithHyphen) {
1975 #else
1976             if (intersect.empty()) {
1977 #endif
1978                 return true;
1979             }
1980 
1981             auto paragraphStyle = fOwner->paragraphStyle();
1982 
1983             // Found a run that intersects with the text
1984             auto context = this->measureTextInsideOneRun(
1985                     intersect, run, runOffsetInLine, 0, true, TextAdjustment::GraphemeGluster);
1986             SkRect clip = context.clip;
1987             clip.offset(lineContext.fTextShift - context.fTextShift, 0);
1988 
1989             switch (rectHeightStyle) {
1990                 case RectHeightStyle::kMax:
1991                     // TODO: Change it once flutter rolls into google3
1992                     //  (probably will break things if changed before)
1993 #ifdef OHOS_SUPPORT
1994                     if (endsWithOnlyHardBreak() && fOwner->paragraphStyle().getParagraphSpacing() > 0) {
1995                         clip.fBottom = this->height() - fOwner->paragraphStyle().getParagraphSpacing();
1996                     } else {
1997                         clip.fBottom = this->height();
1998                     }
1999 #else
2000                     clip.fBottom = this->height();
2001 #endif
2002                     clip.fTop = this->sizes().delta();
2003                     break;
2004                 case RectHeightStyle::kIncludeLineSpacingTop: {
2005                     clip.fBottom = this->height();
2006                     clip.fTop = this->sizes().delta();
2007                     auto verticalShift = this->sizes().rawAscent() - this->sizes().ascent();
2008                     if (isFirstLine()) {
2009                         clip.fTop += verticalShift;
2010                     }
2011                     break;
2012                 }
2013                 case RectHeightStyle::kIncludeLineSpacingMiddle: {
2014                     clip.fBottom = this->height();
2015                     clip.fTop = this->sizes().delta();
2016                     auto verticalShift = this->sizes().rawAscent() - this->sizes().ascent();
2017                     clip.offset(0, verticalShift / 2.0);
2018                     if (isFirstLine()) {
2019                         clip.fTop += verticalShift / 2.0;
2020                     }
2021                     if (isLastLine()) {
2022                         clip.fBottom -= verticalShift / 2.0;
2023                     }
2024                     break;
2025                  }
2026                 case RectHeightStyle::kIncludeLineSpacingBottom: {
2027                     clip.fBottom = this->height();
2028                     clip.fTop = this->sizes().delta();
2029                     auto verticalShift = this->sizes().rawAscent() - this->sizes().ascent();
2030                     clip.offset(0, verticalShift);
2031                     if (isLastLine()) {
2032                         clip.fBottom -= verticalShift;
2033                     }
2034                     break;
2035                 }
2036                 case RectHeightStyle::kStrut: {
2037                     const auto& strutStyle = paragraphStyle.getStrutStyle();
2038                     if (strutStyle.getStrutEnabled()
2039                         && strutStyle.getFontSize() > 0) {
2040                         auto strutMetrics = fOwner->strutMetrics();
2041                         auto top = this->baseline();
2042                         clip.fTop = top + strutMetrics.ascent();
2043                         clip.fBottom = top + strutMetrics.descent();
2044                     }
2045                 }
2046                 break;
2047                 case RectHeightStyle::kTight: {
2048                     if (run->fHeightMultiplier <= 0) {
2049                         break;
2050                     }
2051                     const auto effectiveBaseline = this->baseline() + this->sizes().delta();
2052                     clip.fTop = effectiveBaseline + run->ascent();
2053                     clip.fBottom = effectiveBaseline + run->descent();
2054                 }
2055                 break;
2056                 default:
2057                     SkASSERT(false);
2058                 break;
2059             }
2060 
2061             // Separate trailing spaces and move them in the default order of the paragraph
2062             // in case the run order and the paragraph order don't match
2063             SkRect trailingSpaces = SkRect::MakeEmpty();
2064             if (this->trimmedText().end <this->textWithNewlines().end && // Line has trailing space
2065                 this->textWithNewlines().end == intersect.end &&         // Range is at the end of the line
2066                 this->trimmedText().end > intersect.start)               // Range has more than just spaces
2067             {
2068                 auto delta = this->spacesWidth();
2069                 trailingSpaces = SkRect::MakeXYWH(0, 0, 0, 0);
2070                 // There are trailing spaces in this run
2071                 if (paragraphStyle.getTextAlign() == TextAlign::kJustify && isLastLine())
2072                 {
2073                     // TODO: this is just a patch. Make it right later (when it's clear what and how)
2074                     trailingSpaces = clip;
2075                     if(run->leftToRight()) {
2076                         trailingSpaces.fLeft = this->width();
2077                         clip.fRight = this->width();
2078                     } else {
2079                         trailingSpaces.fRight = 0;
2080                         clip.fLeft = 0;
2081                     }
2082                 } else if (paragraphStyle.getTextDirection() == TextDirection::kRtl &&
2083                     !run->leftToRight())
2084                 {
2085                     // Split
2086                     trailingSpaces = clip;
2087                     trailingSpaces.fLeft = - delta;
2088                     trailingSpaces.fRight = 0;
2089                     clip.fLeft += delta;
2090                 } else if (paragraphStyle.getTextDirection() == TextDirection::kLtr &&
2091                     run->leftToRight())
2092                 {
2093                     // Split
2094                     trailingSpaces = clip;
2095                     trailingSpaces.fLeft = this->width();
2096                     trailingSpaces.fRight = trailingSpaces.fLeft + delta;
2097                     clip.fRight -= delta;
2098                 }
2099             }
2100 
2101             clip.offset(this->offset());
2102             if (trailingSpaces.width() > 0) {
2103                 trailingSpaces.offset(this->offset());
2104             }
2105 
2106             // Check if we can merge two boxes instead of adding a new one
2107             auto merge = [&lastRun, &context, &boxes](SkRect clip) {
2108                 bool mergedBoxes = false;
2109                 if (!boxes.empty() &&
2110                     lastRun != nullptr &&
2111                     context.run->leftToRight() == lastRun->leftToRight() &&
2112                     lastRun->placeholderStyle() == nullptr &&
2113                     context.run->placeholderStyle() == nullptr &&
2114                     nearlyEqual(lastRun->heightMultiplier(),
2115                                 context.run->heightMultiplier()) &&
2116 #ifndef USE_SKIA_TXT
2117                     lastRun->font() == context.run->font())
2118 #else
2119                     IsRSFontEquals(lastRun->font(), context.run->font()))
2120 #endif
2121                 {
2122                     auto& lastBox = boxes.back();
2123                     if (nearlyEqual(lastBox.rect.fTop, clip.fTop) &&
2124                         nearlyEqual(lastBox.rect.fBottom, clip.fBottom) &&
2125                             (nearlyEqual(lastBox.rect.fLeft, clip.fRight) ||
2126                              nearlyEqual(lastBox.rect.fRight, clip.fLeft)))
2127                     {
2128                         lastBox.rect.fLeft = std::min(lastBox.rect.fLeft, clip.fLeft);
2129                         lastBox.rect.fRight = std::max(lastBox.rect.fRight, clip.fRight);
2130                         mergedBoxes = true;
2131                     }
2132                 }
2133                 lastRun = context.run;
2134                 return mergedBoxes;
2135             };
2136 
2137             if (!merge(clip)) {
2138                 boxes.emplace_back(clip, context.run->getTextDirection());
2139             }
2140             if (!nearlyZero(trailingSpaces.width()) && !merge(trailingSpaces)) {
2141                 boxes.emplace_back(trailingSpaces, paragraphStyle.getTextDirection());
2142             }
2143 
2144             if (rectWidthStyle == RectWidthStyle::kMax && !isLastLine()) {
2145                 // Align the very left/right box horizontally
2146                 auto lineStart = this->offset().fX;
2147                 auto lineEnd = this->offset().fX + this->width();
2148                 auto left = boxes[startBox];
2149                 auto right = boxes.back();
2150                 if (left.rect.fLeft > lineStart && left.direction == TextDirection::kRtl) {
2151                     left.rect.fRight = left.rect.fLeft;
2152                     left.rect.fLeft = 0;
2153                     boxes.insert(boxes.begin() + startBox + 1, left);
2154                 }
2155                 if (right.direction == TextDirection::kLtr &&
2156                     right.rect.fRight >= lineEnd &&
2157                     right.rect.fRight < fOwner->widthWithTrailingSpaces()) {
2158                     right.rect.fLeft = right.rect.fRight;
2159                     right.rect.fRight = fOwner->widthWithTrailingSpaces();
2160                     boxes.emplace_back(right);
2161                 }
2162             }
2163 
2164             return true;
2165         });
2166         return true;
2167     });
2168     if (fOwner->getApplyRoundingHack()) {
2169         for (auto& r : boxes) {
2170             r.rect.fLeft = littleRound(r.rect.fLeft);
2171             r.rect.fRight = littleRound(r.rect.fRight);
2172             r.rect.fTop = littleRound(r.rect.fTop);
2173             r.rect.fBottom = littleRound(r.rect.fBottom);
2174         }
2175     }
2176 }
2177 
2178 #ifdef OHOS_SUPPORT
2179 void TextLine::extendCoordinateRange(PositionWithAffinity& positionWithAffinity) {
2180     if (fEllipsis == nullptr) {
2181         return;
2182     }
2183     // Extending coordinate index if the ellipsis's run is selected.
2184     EllipsisModal ellipsisModal = fOwner->paragraphStyle().getEllipsisMod();
2185     if (ellipsisModal == EllipsisModal::TAIL) {
2186         if (static_cast<size_t>(positionWithAffinity.position) > fOwner->getEllipsisTextRange().start &&
2187             static_cast<size_t>(positionWithAffinity.position) <= fOwner->getEllipsisTextRange().end) {
2188             positionWithAffinity.position = static_cast<int32_t>(fOwner->getEllipsisTextRange().end);
2189         }
2190     } else if (ellipsisModal == EllipsisModal::HEAD) {
2191         if (static_cast<size_t>(positionWithAffinity.position) >= fOwner->getEllipsisTextRange().start &&
2192             static_cast<size_t>(positionWithAffinity.position) < fOwner->getEllipsisTextRange().end) {
2193             positionWithAffinity.position = static_cast<int32_t>(fOwner->getEllipsisTextRange().start);
2194         }
2195     }
2196 }
2197 #endif
2198 
2199 PositionWithAffinity TextLine::getGlyphPositionAtCoordinate(SkScalar dx) {
2200 
2201     if (SkScalarNearlyZero(this->width()) && SkScalarNearlyZero(this->spacesWidth())) {
2202         // TODO: this is one of the flutter changes that have to go away eventually
2203         //  Empty line is a special case in txtlib (but only when there are no spaces, too)
2204         auto utf16Index = fOwner->getUTF16Index(this->fTextExcludingSpaces.end);
2205         return { SkToS32(utf16Index) , kDownstream };
2206     }
2207 
2208     PositionWithAffinity result(0, Affinity::kDownstream);
2209 #ifdef OHOS_SUPPORT
2210     this->iterateThroughVisualRuns(EllipsisReadStrategy::READ_REPLACED_WORD, true,
2211 #else
2212     this->iterateThroughVisualRuns(true,
2213 #endif
2214         [this, dx, &result]
2215         (const Run* run, SkScalar runOffsetInLine, TextRange textRange, SkScalar* runWidthInLine) {
2216             bool keepLooking = true;
2217             if (fHyphenRun.get() == run) {
2218                 return keepLooking;
2219             }
2220             *runWidthInLine = this->iterateThroughSingleRunByStyles(
2221             TextAdjustment::GraphemeGluster, run, runOffsetInLine, textRange, StyleType::kNone,
2222             [this, run, dx, &result, &keepLooking]
2223             (TextRange textRange, const TextStyle& style, const TextLine::ClipContext& context0) {
2224 
2225                 SkScalar offsetX = this->offset().fX;
2226                 ClipContext context = context0;
2227 
2228                 // Correct the clip size because libtxt counts trailing spaces
2229                 if (run->leftToRight()) {
2230                     context.clip.fRight += context.fExcludedTrailingSpaces; // extending clip to the right
2231                 } else {
2232                     // Clip starts from 0; we cannot extend it to the left from that
2233                 }
2234                 // However, we need to offset the clip
2235                 context.clip.offset(offsetX, 0.0f);
2236 
2237                 // This patch will help us to avoid a floating point error
2238                 if (SkScalarNearlyEqual(context.clip.fRight, dx, 0.01f)) {
2239                     context.clip.fRight = dx;
2240                 }
2241 
2242                 if (dx <= context.clip.fLeft) {
2243                     // All the other runs are placed right of this one
2244                     auto utf16Index = fOwner->getUTF16Index(context.run->globalClusterIndex(context.pos));
2245                     if (run->leftToRight()) {
2246                         result = { SkToS32(utf16Index), kDownstream };
2247                         keepLooking = false;
2248                     } else {
2249 #ifdef OHOS_SUPPORT
2250                         result = { SkToS32(utf16Index + 1), kUpstream };
2251                         size_t glyphCnt = context.run->glyphs().size();
2252                         if ((glyphCnt != 0) && ((context.run->fUtf8Range.size() / glyphCnt) == EMOJI_WIDTH)) {
2253                             result = { SkToS32(utf16Index + 2), kUpstream };
2254                         }
2255 #else
2256                         result = { SkToS32(utf16Index + 1), kUpstream};
2257 #endif
2258                         // If we haven't reached the end of the run we need to keep looking
2259                         keepLooking = context.pos != 0;
2260                     }
2261                     // For RTL we go another way
2262                     return !run->leftToRight();
2263                 }
2264 
2265                 if (dx >= context.clip.fRight) {
2266                     // We have to keep looking ; just in case keep the last one as the closest
2267 #ifdef OHOS_SUPPORT
2268                     auto utf16Index = fOwner->getUTF16IndexWithOverflowCheck(context.run->globalClusterIndex(context.pos + context.size));
2269 #else
2270                     auto utf16Index = fOwner->getUTF16Index(context.run->globalClusterIndex(context.pos + context.size));
2271 #endif
2272                     if (run->leftToRight()) {
2273                         result = {SkToS32(utf16Index), kUpstream};
2274                     } else {
2275                         result = {SkToS32(utf16Index), kDownstream};
2276                     }
2277                     // For RTL we go another way
2278                     return run->leftToRight();
2279                 }
2280 
2281                 // So we found the run that contains our coordinates
2282                 // Find the glyph position in the run that is the closest left of our point
2283                 // TODO: binary search
2284                 size_t found = context.pos;
2285                 for (size_t index = context.pos; index < context.pos + context.size; ++index) {
2286                     // TODO: this rounding is done to match Flutter tests. Must be removed..
2287                     auto end = context.run->positionX(index) + context.fTextShift + offsetX;
2288                     if (fOwner->getApplyRoundingHack()) {
2289                         end = littleRound(end);
2290                     }
2291                     if (end > dx) {
2292                         break;
2293                     } else if (end == dx && !context.run->leftToRight()) {
2294                         // When we move RTL variable end points to the beginning of the code point which is included
2295                         found = index;
2296                         break;
2297                     }
2298                     found = index;
2299                 }
2300 
2301                 SkScalar glyphemePosLeft = context.run->positionX(found) + context.fTextShift + offsetX;
2302                 SkScalar glyphemesWidth = context.run->positionX(found + 1) - context.run->positionX(found);
2303 
2304                 // Find the grapheme range that contains the point
2305                 auto clusterIndex8 = context.run->globalClusterIndex(found);
2306                 auto clusterEnd8 = context.run->globalClusterIndex(found + 1);
2307                 auto graphemes = fOwner->countSurroundingGraphemes({clusterIndex8, clusterEnd8});
2308 
2309                 SkScalar center = (context.clip.right() + context.clip.left()) / 2;
2310                 if (graphemes.size() > 1) {
2311                     // Calculate the position proportionally based on grapheme count
2312                     SkScalar averageGraphemeWidth = glyphemesWidth / graphemes.size();
2313                     SkScalar delta = dx - glyphemePosLeft;
2314                     int graphemeIndex = SkScalarNearlyZero(averageGraphemeWidth)
2315                                          ? 0
2316                                          : SkScalarFloorToInt(delta / averageGraphemeWidth);
2317                     auto graphemeCenter = glyphemePosLeft + graphemeIndex * averageGraphemeWidth +
2318                                           averageGraphemeWidth * fOwner->getTextSplitRatio();
2319                     auto graphemeUtf8Index = graphemes[graphemeIndex];
2320                     if ((dx < graphemeCenter) == context.run->leftToRight()) {
2321                         size_t utf16Index = fOwner->getUTF16Index(graphemeUtf8Index);
2322                         result = { SkToS32(utf16Index), kDownstream };
2323                     } else {
2324                         size_t utf16Index = fOwner->getUTF16Index(graphemeUtf8Index + 1);
2325                         result = { SkToS32(utf16Index), kUpstream };
2326                     }
2327                     // Keep UTF16 index as is
2328                 } else if ((dx < center) == context.run->leftToRight()) {
2329 #ifdef OHOS_SUPPORT
2330                     size_t utf16Index = fOwner->getUTF16IndexWithOverflowCheck(clusterIndex8);
2331 #else
2332                     size_t utf16Index = fOwner->getUTF16Index(clusterIndex8);
2333 #endif
2334                     result = { SkToS32(utf16Index), kDownstream };
2335                 } else {
2336 #ifdef OHOS_SUPPORT
2337                     size_t utf16Index = 0;
2338                     size_t glyphCnt = context.run->glyphs().size();
2339                     if ((glyphCnt != 0) && !context.run->leftToRight() && ((
2340                         context.run->fUtf8Range.size() / glyphCnt) == EMOJI_WIDTH)) {
2341                         utf16Index = fOwner->getUTF16Index(clusterIndex8) + 2;
2342                     } else if (!context.run->leftToRight()) {
2343                         utf16Index = fOwner->getUTF16Index(clusterIndex8) + 1;
2344                     } else {
2345                         utf16Index = fOwner->getUTF16IndexWithOverflowCheck(clusterEnd8);
2346                     }
2347 #else
2348                     size_t utf16Index = context.run->leftToRight()
2349                                                 ? fOwner->getUTF16Index(clusterEnd8)
2350                                                 : fOwner->getUTF16Index(clusterIndex8) + 1;
2351 #endif
2352                     result = { SkToS32(utf16Index), kUpstream };
2353                 }
2354 
2355                 return keepLooking = false;
2356 
2357             });
2358             return keepLooking;
2359         }
2360     );
2361 
2362 #ifdef OHOS_SUPPORT
2363     extendCoordinateRange(result);
2364 #endif
2365 
2366     return result;
2367 }
2368 
2369 void TextLine::getRectsForPlaceholders(std::vector<TextBox>& boxes) {
2370 #ifdef OHOS_SUPPORT
2371     this->iterateThroughVisualRuns(EllipsisReadStrategy::READ_REPLACED_WORD, true,
2372 #else
2373     this->iterateThroughVisualRuns(true,
2374 #endif
2375         [&boxes, this](const Run* run, SkScalar runOffset, TextRange textRange,
2376                         SkScalar* width) {
2377                 auto context = this->measureTextInsideOneRun(
2378                         textRange, run, runOffset, 0, true, TextAdjustment::GraphemeGluster);
2379                 *width = context.clip.width();
2380 
2381             if (textRange.width() == 0) {
2382                 return true;
2383             }
2384             if (!run->isPlaceholder()) {
2385                 return true;
2386             }
2387 
2388             SkRect clip = context.clip;
2389             clip.offset(this->offset());
2390 
2391             if (fOwner->getApplyRoundingHack()) {
2392                 clip.fLeft = littleRound(clip.fLeft);
2393                 clip.fRight = littleRound(clip.fRight);
2394                 clip.fTop = littleRound(clip.fTop);
2395                 clip.fBottom = littleRound(clip.fBottom);
2396             }
2397             boxes.emplace_back(clip, run->getTextDirection());
2398             return true;
2399         });
2400 }
2401 
2402 size_t TextLine::getGlyphCount() const
2403 {
2404     size_t glyphCount = 0;
2405     for (auto& blob: fTextBlobCache) {
2406         glyphCount += blob.fVisitor_Size;
2407     }
2408     return glyphCount;
2409 }
2410 
2411 #ifdef OHOS_SUPPORT
2412 std::vector<std::unique_ptr<RunBase>> TextLine::getGlyphRuns() const
2413 {
2414     std::vector<std::unique_ptr<RunBase>> runBases;
2415     size_t num = 0;
2416     // Gets the offset position of the current line across the paragraph
2417     size_t pos = fClusterRange.start;
2418     size_t trailSpaces = 0;
2419     for (auto& blob: fTextBlobCache) {
2420         ++num;
2421         if (blob.fVisitor_Size == 0) {
2422             continue;
2423         }
2424         if (num == fTextBlobCache.size()) {
2425             // Counts how many tabs have been removed from the end of the current line
2426             trailSpaces = fGhostClusterRange.width() - fClusterRange.width();
2427         }
2428         std::unique_ptr<RunBaseImpl> runBaseImplPtr = std::make_unique<RunBaseImpl>(
2429             blob.fBlob, blob.fOffset, blob.fPaint, blob.fClippingNeeded, blob.fClipRect,
2430             blob.fVisitor_Run, blob.fVisitor_Pos, pos, trailSpaces, blob.fVisitor_Size);
2431 
2432         // Calculate the position of each blob, relative to the entire paragraph
2433         pos += blob.fVisitor_Size;
2434         runBases.emplace_back(std::move(runBaseImplPtr));
2435     }
2436     return runBases;
2437 }
2438 #else
2439 std::vector<std::unique_ptr<RunBase>> TextLine::getGlyphRuns() const
2440 {
2441     std::vector<std::unique_ptr<RunBase>> runBases;
2442     for (auto& blob: fTextBlobCache) {
2443         std::unique_ptr<RunBaseImpl> runBaseImplPtr = std::make_unique<RunBaseImpl>(
2444             blob.fBlob, blob.fOffset, blob.fPaint, blob.fClippingNeeded, blob.fClipRect,
2445             blob.fVisitor_Run, blob.fVisitor_Pos, blob.fVisitor_Size);
2446         runBases.emplace_back(std::move(runBaseImplPtr));
2447     }
2448     return runBases;
2449 }
2450 #endif
2451 
2452 #ifdef OHOS_SUPPORT
2453 int getEndWhitespaceCount(const ClusterRange& range, ParagraphImpl* owner)
2454 {
2455     if (owner == nullptr) {
2456         return 0;
2457     }
2458 
2459     int endWhitespaceCount = 0;
2460     for (auto clusterIndex = range.end - 1; clusterIndex >= range.start; clusterIndex--) {
2461         if (!owner->cluster(clusterIndex).isWhitespaceBreak()) {
2462             break;
2463         }
2464 
2465         endWhitespaceCount++;
2466         if (clusterIndex == range.start) {
2467             break;
2468         }
2469     }
2470 
2471     return endWhitespaceCount;
2472 }
2473 
2474 std::unique_ptr<TextLineBase> TextLine::createTruncatedLine(double width, EllipsisModal ellipsisMode,
2475     const std::string& ellipsisStr)
2476 {
2477     if (width > 0 && (ellipsisMode == EllipsisModal::HEAD || ellipsisMode == EllipsisModal::TAIL)) {
2478         TextLine textLine = CloneSelf();
2479         SkScalar widthVal = static_cast<SkScalar>(width);
2480         if (widthVal < widthWithEllipsisSpaces() && !ellipsisStr.empty()) {
2481             if (ellipsisMode == EllipsisModal::HEAD) {
2482                 textLine.fIsTextLineEllipsisHeadModal = true;
2483                 textLine.setTextBlobCachePopulated(false);
2484                 textLine.createHeadEllipsis(widthVal, SkString(ellipsisStr), true);
2485             } else if (ellipsisMode == EllipsisModal::TAIL) {
2486                 textLine.fIsTextLineEllipsisHeadModal = false;
2487                 textLine.setTextBlobCachePopulated(false);
2488                 int endWhitespaceCount = getEndWhitespaceCount(fGhostClusterRange, fOwner);
2489                 textLine.fGhostClusterRange.end -= static_cast<size_t>(endWhitespaceCount);
2490                 textLine.createTailEllipsis(widthVal, SkString(ellipsisStr), true, fOwner->getWordBreakType());
2491             }
2492         }
2493         return std::make_unique<TextLineBaseImpl>(std::make_unique<TextLine>(std::move(textLine)));
2494     }
2495 
2496     return nullptr;
2497 }
2498 
2499 double TextLine::getTypographicBounds(double* ascent, double* descent, double* leading) const
2500 {
2501     if (ascent == nullptr || descent == nullptr || leading == nullptr) {
2502         return 0.0;
2503     }
2504 
2505     *ascent = fMaxRunMetrics.ascent();
2506     *descent = fMaxRunMetrics.descent();
2507     *leading = fMaxRunMetrics.leading();
2508     return widthWithEllipsisSpaces();
2509 }
2510 
2511 size_t getPrevGlyphsIndex(const ClusterRange& range, ParagraphImpl* owner, RunIndex& prevRunIndex)
2512 {
2513     if (owner == nullptr) {
2514         return 0;
2515     }
2516 
2517     size_t glyphsIndex = 0;
2518     auto clusterIndex = range.start - 1;
2519     prevRunIndex = owner->cluster(clusterIndex).runIndex();
2520     if (prevRunIndex != owner->cluster(range.start).runIndex()) {
2521         // Belongs to a different run.
2522         return 0;
2523     }
2524 
2525     for (; clusterIndex >= 0; clusterIndex--) {
2526         RunIndex runIndex = owner->cluster(clusterIndex).runIndex();
2527         if (prevRunIndex != runIndex) {
2528             // Found a different run.
2529             break;
2530         }
2531 
2532         glyphsIndex++;
2533 
2534         if (clusterIndex == 0) {
2535             // All belong to the first run.
2536             break;
2537         }
2538     }
2539 
2540     return glyphsIndex;
2541 }
2542 
2543 #ifndef USE_SKIA_TXT
2544 std::vector<SkRect> getAllRectInfo(const ClusterRange& range, ParagraphImpl* owner)
2545 {
2546     std::vector<SkRect> rectVec;
2547 #else
2548 std::vector<RSRect> getAllRectInfo(const ClusterRange& range, ParagraphImpl* owner)
2549 {
2550     std::vector<RSRect> rectVec;
2551 #endif
2552     if (owner == nullptr) {
2553         return rectVec;
2554     }
2555 
2556     // If it is not the first line, you need to get the GlyphsIndex of the first character.
2557     size_t glyphsIndex  = 0;
2558     RunIndex prevRunIndex = 0;
2559     if (range.start > 0) {
2560         glyphsIndex = getPrevGlyphsIndex(range, owner, prevRunIndex);
2561     }
2562 
2563     for (auto clusterIndex = range.start; clusterIndex < range.end; clusterIndex++) {
2564         RunIndex runIndex = owner->cluster(clusterIndex).runIndex();
2565         if (prevRunIndex != runIndex) {
2566             glyphsIndex = 0;
2567         }
2568 
2569         auto run = owner->cluster(clusterIndex).runOrNull();
2570         if (run == nullptr) {
2571             break;
2572         }
2573 
2574         SkGlyphID glyphId = run->glyphs()[glyphsIndex];
2575 #ifndef USE_SKIA_TXT
2576         SkRect glyphBounds;
2577         run->font().getBounds(&glyphId, 1, &glyphBounds, nullptr);
2578 #else
2579         RSRect glyphBounds;
2580         run->font().GetWidths(&glyphId, 1, nullptr, &glyphBounds);
2581 #endif
2582         rectVec.push_back(glyphBounds);
2583         glyphsIndex++;
2584         prevRunIndex = runIndex;
2585     }
2586 
2587     return rectVec;
2588 }
2589 
2590 RSRect TextLine::getImageBounds() const
2591 {
2592     // Look for the first non-space character from the end and get its advance and index
2593     // to calculate the final image bounds.
2594     SkRect rect = {0.0, 0.0, 0.0, 0.0};
2595     int endWhitespaceCount = getEndWhitespaceCount(fGhostClusterRange, fOwner);
2596     size_t endWhitespaceCountVal = static_cast<size_t>(endWhitespaceCount);
2597     if (endWhitespaceCountVal == (fGhostClusterRange.end - fGhostClusterRange.start)) {
2598         // Full of Spaces.
2599         return {};
2600     }
2601     SkScalar endAdvance = fOwner->cluster(fGhostClusterRange.end - endWhitespaceCountVal - 1).width();
2602 
2603     // The first space width of the line needs to be added to the x value.
2604     SkScalar startWhitespaceAdvance = 0.0;
2605     size_t startWhitespaceCount = 0;
2606     for (auto clusterIndex = fGhostClusterRange.start; clusterIndex < fGhostClusterRange.end; clusterIndex++) {
2607         if (fOwner->cluster(clusterIndex).isWhitespaceBreak()) {
2608             startWhitespaceAdvance += fOwner->cluster(clusterIndex).width();
2609             startWhitespaceCount++;
2610         } else {
2611             break;
2612         }
2613     }
2614 
2615     // Gets rect information for all characters in line.
2616     auto rectVec = getAllRectInfo(fGhostClusterRange, fOwner);
2617     // Calculate the final y and height.
2618     auto joinRect = rectVec[startWhitespaceCount];
2619     for (size_t i = startWhitespaceCount + 1; i < rectVec.size() - endWhitespaceCountVal; ++i) {
2620         joinRect.Join(rectVec[i]);
2621     }
2622 
2623     SkScalar lineWidth = width();
2624     auto endRect = rectVec[rectVec.size() - endWhitespaceCountVal - 1];
2625 #ifndef USE_SKIA_TXT
2626     SkScalar x = rectVec[startWhitespaceCount].x() + startWhitespaceAdvance;
2627     SkScalar y = joinRect.bottom();
2628     SkScalar width = lineWidth - (endAdvance - endRect.x() - endRect.width()) - x;
2629     SkScalar height = joinRect.height();
2630 #else
2631     SkScalar x = rectVec[startWhitespaceCount].GetLeft() + startWhitespaceAdvance;
2632     SkScalar y = joinRect.GetBottom();
2633     SkScalar width = lineWidth - (endAdvance - endRect.GetLeft() - endRect.GetWidth()) - x;
2634     SkScalar height = joinRect.GetHeight();
2635 #endif
2636 
2637     rect.setXYWH(x, y, width, height);
2638     return {rect.fLeft, rect.fTop, rect.fRight, rect.fBottom};
2639 }
2640 
2641 double TextLine::getTrailingSpaceWidth() const
2642 {
2643     return spacesWidth();
2644 }
2645 
2646 int32_t TextLine::getStringIndexForPosition(SkPoint point) const
2647 {
2648     int32_t index = fGhostClusterRange.start;
2649     double offset = point.x();
2650     if (offset >= widthWithEllipsisSpaces()) {
2651         index = fGhostClusterRange.end;
2652     } else if (offset > 0) {
2653         double curOffset = 0.0;
2654         for (auto clusterIndex = fGhostClusterRange.start; clusterIndex < fGhostClusterRange.end; ++clusterIndex) {
2655             double characterWidth = fOwner->cluster(clusterIndex).width();
2656             if (offset <= curOffset + characterWidth / 2) {
2657                 return index;
2658             }
2659             index++;
2660             curOffset += characterWidth;
2661         }
2662     }
2663 
2664     return index;
2665 }
2666 
2667 double TextLine::getOffsetForStringIndex(int32_t index) const
2668 {
2669     double offset = 0.0;
2670     if (index <= 0) {
2671         return offset;
2672     }
2673 
2674     size_t indexVal = static_cast<size_t>(index);
2675     if (indexVal >= fGhostClusterRange.end) {
2676         offset = widthWithEllipsisSpaces();
2677     } else if (indexVal > fGhostClusterRange.start) {
2678         size_t clusterIndex = fGhostClusterRange.start;
2679         while (clusterIndex < fGhostClusterRange.end) {
2680             offset += fOwner->cluster(clusterIndex).width();
2681             if (++clusterIndex == indexVal) {
2682                 break;
2683             }
2684         }
2685     }
2686 
2687     return offset;
2688 }
2689 
2690 std::map<int32_t, double> TextLine::getIndexAndOffsets(bool& isHardBreak) const
2691 {
2692     std::map<int32_t, double> offsetMap;
2693     double offset = 0.0;
2694     for (auto clusterIndex = fGhostClusterRange.start; clusterIndex < fGhostClusterRange.end; ++clusterIndex) {
2695         auto& cluster = fOwner->cluster(clusterIndex);
2696         offset += cluster.width();
2697         isHardBreak = cluster.isHardBreak();
2698         if (!isHardBreak) {
2699             offsetMap[clusterIndex] = offset;
2700         }
2701     }
2702     return offsetMap;
2703 }
2704 
2705 double TextLine::getAlignmentOffset(double alignmentFactor, double alignmentWidth) const
2706 {
2707     double lineWidth = width();
2708     if (alignmentWidth <= lineWidth) {
2709         return 0.0;
2710     }
2711 
2712     double offset = 0.0;
2713     TextDirection textDirection = fOwner->paragraphStyle().getTextDirection();
2714     if (alignmentFactor <= 0) {
2715         // Flush left.
2716         if (textDirection == TextDirection::kRtl) {
2717             offset =  lineWidth - alignmentWidth;
2718         }
2719     } else if (alignmentFactor < 1) {
2720         // Align according to the alignmentFactor.
2721         if (textDirection == TextDirection::kLtr) {
2722             offset = (alignmentWidth - lineWidth) * alignmentFactor;
2723         } else {
2724             offset = (lineWidth - alignmentWidth) * (1 - alignmentFactor);
2725         }
2726     } else {
2727         // Flush right.
2728         if (textDirection == TextDirection::kLtr) {
2729             offset = alignmentWidth - lineWidth;
2730         }
2731     }
2732 
2733     return offset;
2734 }
2735 
2736 SkRect TextLine::computeShadowRect(SkScalar x, SkScalar y, const TextStyle& style, const ClipContext& context) const
2737 {
2738     SkScalar offsetX = x + this->fOffset.fX;
2739     SkScalar offsetY = y + this->fOffset.fY -
2740         (context.run ? context.run->fCompressionBaselineShift : 0);
2741     SkRect shadowRect = SkRect::MakeEmpty();
2742 
2743     for (const TextShadow& shadow : style.getShadows()) {
2744         if (!shadow.hasShadow()) {
2745             continue;
2746         }
2747 
2748         SkScalar blurSigma = SkDoubleToScalar(shadow.fBlurSigma);
2749         SkRect rect = context.clip
2750             .makeOffset(offsetX + shadow.fOffset.fX, offsetY + shadow.fOffset.fY)
2751             .makeOutset(blurSigma, blurSigma);
2752         shadowRect.join(rect);
2753     }
2754     return shadowRect;
2755 }
2756 
2757 SkRect TextLine::getAllShadowsRect(SkScalar x, SkScalar y) const
2758 {
2759     if (!fHasShadows) {
2760         return SkRect::MakeEmpty();
2761     }
2762     SkRect paintRegion = SkRect::MakeEmpty();
2763     this->iterateThroughVisualRuns(EllipsisReadStrategy::READ_REPLACED_WORD, false,
2764         [&paintRegion, x, y, this]
2765         (const Run* run, SkScalar runOffsetInLine, TextRange textRange, SkScalar* runWidthInLine) {
2766             if (runWidthInLine == nullptr) {
2767                 return true;
2768             }
2769             *runWidthInLine = this->iterateThroughSingleRunByStyles(
2770                 TextAdjustment::GlyphCluster, run, runOffsetInLine, textRange, StyleType::kShadow,
2771                 [&paintRegion, x, y, this]
2772                 (TextRange textRange, const TextStyle& style, const ClipContext& context) {
2773                     SkRect rect = computeShadowRect(x, y, style, context);
2774                     paintRegion.join(rect);
2775                 });
2776             return true;
2777         });
2778     return paintRegion;
2779 }
2780 
2781 SkRect TextLine::generatePaintRegion(SkScalar x, SkScalar y)
2782 {
2783     SkRect paintRegion = SkRect::MakeXYWH(x, y, 0, 0);
2784     fIsArcText = false;
2785 
2786     SkRect rect = getAllShadowsRect(x, y);
2787     paintRegion.join(rect);
2788 
2789     // textblob
2790     this->ensureTextBlobCachePopulated();
2791     for (auto& record : fTextBlobCache) {
2792         rect = GetTextBlobSkTightBound(record.fBlob, x + record.fOffset.fX, y + record.fOffset.fY, record.fClipRect);
2793         paintRegion.join(rect);
2794     }
2795 
2796     return paintRegion;
2797 }
2798 #endif
2799 
2800 TextLine TextLine::CloneSelf()
2801 {
2802     TextLine textLine;
2803     textLine.fBlockRange = this->fBlockRange;
2804     textLine.fTextExcludingSpaces = this->fTextExcludingSpaces;
2805     textLine.fText = this->fText;
2806     textLine.fTextIncludingNewlines = this->fTextIncludingNewlines;
2807     textLine.fClusterRange = this->fClusterRange;
2808 
2809     textLine.fGhostClusterRange = this->fGhostClusterRange;
2810     textLine.fRunsInVisualOrder = this->fRunsInVisualOrder;
2811     textLine.fAdvance = this->fAdvance;
2812     textLine.fOffset = this->fOffset;
2813     textLine.fShift = this->fShift;
2814 
2815     textLine.fWidthWithSpaces = this->fWidthWithSpaces;
2816     if (this->fEllipsis) {
2817         textLine.fEllipsis = std::make_unique<Run>(*this->fEllipsis);
2818     }
2819 
2820     textLine.fSizes = this->fSizes;
2821     textLine.fMaxRunMetrics = this->fMaxRunMetrics;
2822     textLine.fHasBackground = this->fHasBackground;
2823     textLine.fHasShadows = this->fHasShadows;
2824     textLine.fHasDecorations = this->fHasDecorations;
2825     textLine.fAscentStyle = this->fAscentStyle;
2826     textLine.fDescentStyle = this->fDescentStyle;
2827     textLine.fTextBlobCachePopulated = this->fTextBlobCachePopulated;
2828 #ifdef OHOS_SUPPORT
2829     textLine.fOwner = this->fOwner;
2830     textLine.fIsTextLineEllipsisHeadModal = this->fIsTextLineEllipsisHeadModal;
2831     textLine.fEllipsisString = this->fEllipsisString;
2832     textLine.fBreakWithHyphen = this->fBreakWithHyphen;
2833     if (this->fHyphenRun) {
2834         textLine.fHyphenRun = std::make_unique<Run>(*this->fHyphenRun);
2835     }
2836     textLine.fHyphenIndex = this->fHyphenIndex;
2837 #endif
2838 
2839     textLine.roundRectAttrs = this->roundRectAttrs;
2840     textLine.fTextBlobCache = this->fTextBlobCache;
2841     textLine.fTextRangeReplacedByEllipsis = this->fTextRangeReplacedByEllipsis;
2842     textLine.fEllipsisIndex = this->fEllipsisIndex;
2843     textLine.fLastClipRunLtr = this->fLastClipRunLtr;
2844     return textLine;
2845 }
2846 
2847 #ifdef OHOS_SUPPORT
2848 void TextLine::setBreakWithHyphen(bool breakWithHyphen)
2849 {
2850     fBreakWithHyphen = breakWithHyphen;
2851     if (!breakWithHyphen) {
2852         if (fHyphenRun != nullptr) {
2853             fWidthWithSpaces -= fHyphenRun->fAdvance.fX;
2854         }
2855         fHyphenRun.reset();
2856         fHyphenIndex = EMPTY_INDEX;
2857     } else {
2858         auto endIx = fClusterRange.end - 1;
2859         // if we don't have hyphen run, shape it
2860         auto& cluster = fOwner->cluster(endIx);
2861         SkString dash("-");
2862         if (fHyphenRun == nullptr) {
2863             fHyphenRun = shapeString(dash, &cluster);
2864             fHyphenRun->setOwner(fOwner);
2865         }
2866 
2867         fHyphenRun->fTextRange = TextRange(cluster.textRange().end, cluster.textRange().end + 1);
2868         fHyphenRun->fClusterStart = cluster.textRange().end;
2869 
2870         fAdvance.fX += fHyphenRun->fAdvance.fX;
2871         fWidthWithSpaces = fAdvance.fX;
2872         fGhostClusterRange.end = fClusterRange.end;
2873         fHyphenIndex = cluster.runIndex();
2874         fText.end = cluster.textRange().end;
2875         fTextIncludingNewlines.end = cluster.textRange().end;
2876         fTextExcludingSpaces.end = cluster.textRange().end;
2877     }
2878 }
2879 
2880 bool TextLine::getBreakWithHyphen() const
2881 {
2882     return fBreakWithHyphen;
2883 }
2884 
2885 void TextLine::updateTextLinePaintAttributes() {
2886     fHasBackground = false;
2887     fHasDecorations = false;
2888     fHasShadows = false;
2889     for (BlockIndex index = fBlockRange.start; index < fBlockRange.end; ++index) {
2890         auto textStyleBlock = fOwner->styles().begin() + index;
2891         if (textStyleBlock->fStyle.hasBackground()) {
2892             fHasBackground = true;
2893         }
2894         if (textStyleBlock->fStyle.getDecorationType() != TextDecoration::kNoDecoration &&
2895             textStyleBlock->fStyle.getDecorationThicknessMultiplier() > 0) {
2896             fHasDecorations = true;
2897         }
2898         if (textStyleBlock->fStyle.getShadowNumber() > 0) {
2899             fHasShadows = true;
2900         }
2901     }
2902 }
2903 #endif
2904 
2905 }  // namespace textlayout
2906 }  // namespace skia
2907