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