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