• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2019 Google LLC.
2 
3 #include "include/core/SkBlurTypes.h"
4 #include "include/core/SkFont.h"
5 #include "include/core/SkFontMetrics.h"
6 #include "include/core/SkMaskFilter.h"
7 #include "include/core/SkPaint.h"
8 #include "include/core/SkSpan.h"
9 #include "include/core/SkString.h"
10 #include "include/core/SkTextBlob.h"
11 #include "include/core/SkTypes.h"
12 #include "include/private/SkTemplates.h"
13 #include "include/private/SkTo.h"
14 #include "modules/skparagraph/include/DartTypes.h"
15 #include "modules/skparagraph/include/Metrics.h"
16 #include "modules/skparagraph/include/ParagraphPainter.h"
17 #include "modules/skparagraph/include/ParagraphStyle.h"
18 #include "modules/skparagraph/include/TextShadow.h"
19 #include "modules/skparagraph/include/TextStyle.h"
20 #include "modules/skparagraph/src/Decorations.h"
21 #include "modules/skparagraph/src/ParagraphImpl.h"
22 #include "modules/skparagraph/src/ParagraphPainterImpl.h"
23 #include "modules/skparagraph/src/RunBaseImpl.h"
24 #include "modules/skparagraph/src/TextLine.h"
25 #include "modules/skshaper/include/SkShaper.h"
26 
27 #include <algorithm>
28 #include <iterator>
29 #include <limits>
30 #include <map>
31 #include <memory>
32 #include <tuple>
33 #include <type_traits>
34 #include <utility>
35 
36 namespace skia {
37 namespace textlayout {
38 #define MAX_INT_VALUE 0x7FFFFFFF
39 
40 namespace {
41 
42 // TODO: deal with all the intersection functionality
intersected(const TextRange & a,const TextRange & b)43 TextRange intersected(const TextRange& a, const TextRange& b) {
44     if (a.start == b.start && a.end == b.end) return a;
45     auto begin = std::max(a.start, b.start);
46     auto end = std::min(a.end, b.end);
47     return end >= begin ? TextRange(begin, end) : EMPTY_TEXT;
48 }
49 
littleRound(SkScalar a)50 SkScalar littleRound(SkScalar a) {
51     // This rounding is done to match Flutter tests. Must be removed..
52   return SkScalarRoundToScalar(a * 100.0)/100.0;
53 }
54 
operator *(const TextRange & a,const TextRange & b)55 TextRange operator*(const TextRange& a, const TextRange& b) {
56     if (a.start == b.start && a.end == b.end) return a;
57     auto begin = std::max(a.start, b.start);
58     auto end = std::min(a.end, b.end);
59     return end > begin ? TextRange(begin, end) : EMPTY_TEXT;
60 }
61 
compareRound(SkScalar a,SkScalar b,bool applyRoundingHack)62 int compareRound(SkScalar a, SkScalar b, bool applyRoundingHack) {
63     // There is a rounding error that gets bigger when maxWidth gets bigger
64     // VERY long zalgo text (> 100000) on a VERY long line (> 10000)
65     // Canvas scaling affects it
66     // Letter spacing affects it
67     // It has to be relative to be useful
68     auto base = std::max(SkScalarAbs(a), SkScalarAbs(b));
69     auto diff = SkScalarAbs(a - b);
70     if (nearlyZero(base) || diff / base < 0.001f) {
71         return 0;
72     }
73 
74     auto ra = a;
75     auto rb = b;
76 
77     if (applyRoundingHack) {
78         ra = littleRound(a);
79         rb = littleRound(b);
80     }
81     if (ra < rb) {
82         return -1;
83     } else {
84         return 1;
85     }
86 }
87 
88 #ifdef USE_SKIA_TXT
IsRSFontEquals(const RSFont & font0,const RSFont & font1)89 bool IsRSFontEquals(const RSFont& font0, const RSFont& font1) {
90     auto f0 = const_cast<RSFont&>(font0);
91     auto f1 = const_cast<RSFont&>(font1);
92     return f0.GetTypeface().get() == f1.GetTypeface().get() &&
93         f0.GetSize() == f1.GetSize() &&
94         f0.GetScaleX() == f1.GetScaleX() &&
95         f0.GetSkewX() == f1.GetSkewX() &&
96         f0.GetEdging() == f1.GetEdging() &&
97         f0.GetHinting() == f1.GetHinting();
98 }
99 #endif
100 
101 }  // namespace
102 
TextLine(ParagraphImpl * owner,SkVector offset,SkVector advance,BlockRange blocks,TextRange textExcludingSpaces,TextRange text,TextRange textIncludingNewlines,ClusterRange clusters,ClusterRange clustersWithGhosts,SkScalar widthWithSpaces,InternalLineMetrics sizes)103 TextLine::TextLine(ParagraphImpl* owner,
104                    SkVector offset,
105                    SkVector advance,
106                    BlockRange blocks,
107                    TextRange textExcludingSpaces,
108                    TextRange text,
109                    TextRange textIncludingNewlines,
110                    ClusterRange clusters,
111                    ClusterRange clustersWithGhosts,
112                    SkScalar widthWithSpaces,
113                    InternalLineMetrics sizes)
114         : fOwner(owner)
115         , fBlockRange(blocks)
116         , fTextExcludingSpaces(textExcludingSpaces)
117         , fText(text)
118         , fTextIncludingNewlines(textIncludingNewlines)
119         , fClusterRange(clusters)
120         , fGhostClusterRange(clustersWithGhosts)
121         , fRunsInVisualOrder()
122         , fAdvance(advance)
123         , fOffset(offset)
124         , fShift(0.0)
125         , fWidthWithSpaces(widthWithSpaces)
126         , fEllipsis(nullptr)
127         , fSizes(sizes)
128         , fHasBackground(false)
129         , fHasShadows(false)
130         , fHasDecorations(false)
131         , fAscentStyle(LineMetricStyle::CSS)
132         , fDescentStyle(LineMetricStyle::CSS)
133         , fTextBlobCachePopulated(false) {
134     // Reorder visual runs
135     auto& start = owner->cluster(fGhostClusterRange.start);
136     auto& end = owner->cluster(fGhostClusterRange.end - 1);
137     size_t numRuns = end.runIndex() - start.runIndex() + 1;
138 
139     for (BlockIndex index = fBlockRange.start; index < fBlockRange.end; ++index) {
140         auto b = fOwner->styles().begin() + index;
141         if (b->fStyle.hasBackground()) {
142             fHasBackground = true;
143         }
144         if (b->fStyle.getDecorationType() != TextDecoration::kNoDecoration) {
145             fHasDecorations = true;
146         }
147         if (b->fStyle.getShadowNumber() > 0) {
148             fHasShadows = true;
149         }
150     }
151 
152     // Get the logical order
153 
154     // This is just chosen to catch the common/fast cases. Feel free to tweak.
155     constexpr int kPreallocCount = 4;
156     SkAutoSTArray<kPreallocCount, SkUnicode::BidiLevel> runLevels(numRuns);
157     std::vector<RunIndex> placeholdersInOriginalOrder;
158     size_t runLevelsIndex = 0;
159     // Placeholders must be laid out using the original order in which they were added
160     // in the input. The API does not provide a way to indicate that a placeholder
161     // position was moved due to bidi reordering.
162     for (auto runIndex = start.runIndex(); runIndex <= end.runIndex(); ++runIndex) {
163         auto& run = fOwner->run(runIndex);
164         runLevels[runLevelsIndex++] = run.fBidiLevel;
165         fMaxRunMetrics.add(
166             InternalLineMetrics(run.correctAscent(), run.correctDescent(), run.fFontMetrics.fLeading));
167         if (run.isPlaceholder()) {
168             placeholdersInOriginalOrder.push_back(runIndex);
169         }
170     }
171     SkASSERT(runLevelsIndex == numRuns);
172 
173     SkAutoSTArray<kPreallocCount, int32_t> logicalOrder(numRuns);
174 
175     // TODO: hide all these logic in SkUnicode?
176     fOwner->getUnicode()->reorderVisual(runLevels.data(), numRuns, logicalOrder.data());
177     auto firstRunIndex = start.runIndex();
178     auto placeholderIter = placeholdersInOriginalOrder.begin();
179     for (auto index : logicalOrder) {
180         auto runIndex = firstRunIndex + index;
181         if (fOwner->run(runIndex).isPlaceholder()) {
182             fRunsInVisualOrder.push_back(*placeholderIter++);
183         } else {
184             fRunsInVisualOrder.push_back(runIndex);
185         }
186     }
187 
188     // TODO: This is the fix for flutter. Must be removed...
189     for (auto cluster = &start; cluster <= &end; ++cluster) {
190         if (!cluster->run().isPlaceholder()) {
191             fShift += cluster->getHalfLetterSpacing();
192             break;
193         }
194     }
195     fTextRangeReplacedByEllipsis = EMPTY_RANGE;
196 }
197 
paint(ParagraphPainter * painter,SkScalar x,SkScalar y)198 void TextLine::paint(ParagraphPainter* painter, SkScalar x, SkScalar y) {
199     prepareRoundRect();
200 
201     this->iterateThroughVisualRuns(false,
202         [painter, x, y, this]
203         (const Run* run, SkScalar runOffsetInLine, TextRange textRange, SkScalar* runWidthInLine) {
204             *runWidthInLine = this->iterateThroughSingleRunByStyles(
205             TextAdjustment::GlyphCluster, run, runOffsetInLine, textRange, StyleType::kBackground,
206             [painter, x, y, run, this](TextRange textRange, const TextStyle& style, const ClipContext& context) {
207                 if (fHasBackground) {
208                     this->paintBackground(painter, x, y, textRange, style, context);
209                 }
210                 paintRoundRect(painter, x, y, run);
211             });
212         return true;
213         });
214 
215     if (fHasShadows) {
216         this->iterateThroughVisualRuns(false,
217             [painter, x, y, this]
218             (const Run* run, SkScalar runOffsetInLine, TextRange textRange, SkScalar* runWidthInLine) {
219             *runWidthInLine = this->iterateThroughSingleRunByStyles(
220                 TextAdjustment::GlyphCluster, run, runOffsetInLine, textRange, StyleType::kShadow,
221                 [painter, x, y, this]
222                 (TextRange textRange, const TextStyle& style, const ClipContext& context) {
223                     this->paintShadow(painter, x, y, textRange, style, context);
224                 });
225             return true;
226             });
227     }
228 
229     this->ensureTextBlobCachePopulated();
230 
231     for (auto& record : fTextBlobCache) {
232         record.paint(painter, x, y);
233     }
234 
235     if (fHasDecorations) {
236         this->iterateThroughVisualRuns(true,
237             [painter, x, y, this]
238             (const Run* run, SkScalar runOffsetInLine, TextRange textRange, SkScalar* runWidthInLine) {
239                 *runWidthInLine = this->iterateThroughSingleRunByStyles(
240                 TextAdjustment::GlyphCluster, run, runOffsetInLine, textRange, StyleType::kDecorations,
241                 [painter, x, y, this]
242                 (TextRange textRange, const TextStyle& style, const ClipContext& context) {
243                     this->paintDecorations(painter, x, y, textRange, style, context);
244                 });
245                 return true;
246         });
247     }
248 }
249 
hasBackgroundRect(const RoundRectAttr & attr)250 bool TextLine::hasBackgroundRect(const RoundRectAttr& attr) {
251     return attr.roundRectStyle.color != 0 && attr.rect.width() > 0;
252 }
253 
computeRoundRect(int & index,int & preIndex,std::vector<Run * > & groupRuns,Run * run)254 void TextLine::computeRoundRect(int& index, int& preIndex, std::vector<Run*>& groupRuns, Run* run) {
255     int runCount = roundRectAttrs.size();
256     if (index >= runCount) {
257         return;
258     }
259 
260     bool leftRound = false;
261     bool rightRound = false;
262     if (hasBackgroundRect(roundRectAttrs[index])) {
263         int styleId = roundRectAttrs[index].styleId;
264         // index - 1 is previous index, -1 is the invalid styleId
265         int preStyleId = index == 0 ? -1 : roundRectAttrs[index - 1].styleId;
266         // runCount - 1 is the last run index, index + 1 is next run index, -1 is the invalid styleId
267         int nextStyleId = index == runCount - 1 ? -1 : roundRectAttrs[index + 1].styleId;
268         // index - preIndex > 1 means the left run has no background rect
269         leftRound = (preIndex < 0 || index - preIndex > 1 || preStyleId != styleId);
270         // runCount - 1 is the last run index
271         rightRound = (index == runCount - 1 || !hasBackgroundRect(roundRectAttrs[index + 1]) ||
272             nextStyleId != styleId);
273         preIndex = index;
274         groupRuns.push_back(run);
275     } else if (!groupRuns.empty()) {
276         groupRuns.erase(groupRuns.begin(), groupRuns.end());
277     }
278     if (leftRound && rightRound) {
279         run->setRoundRectType(RoundRectType::ALL);
280     } else if (leftRound) {
281         run->setRoundRectType(RoundRectType::LEFT_ONLY);
282     } else if (rightRound) {
283         run->setRoundRectType(RoundRectType::RIGHT_ONLY);
284     } else {
285         run->setRoundRectType(RoundRectType::NONE);
286     }
287 
288     if (rightRound && !groupRuns.empty()) {
289         double maxRoundRectRadius = MAX_INT_VALUE;
290         double minTop = MAX_INT_VALUE;
291         double maxBottom = 0;
292         for (auto &gRun : groupRuns) {
293             RoundRectAttr& attr = roundRectAttrs[gRun->getIndexInLine()];
294             maxRoundRectRadius = std::fmin(std::fmin(attr.rect.width(), attr.rect.height()), maxRoundRectRadius);
295             minTop = std::fmin(minTop, attr.rect.top());
296             maxBottom = std::fmax(maxBottom, attr.rect.bottom());
297         }
298         for (auto &gRun : groupRuns) {
299             gRun->setMaxRoundRectRadius(maxRoundRectRadius);
300             gRun->setTopInGroup(minTop - gRun->offset().y());
301             gRun->setBottomInGroup(maxBottom - gRun->offset().y());
302         }
303         groupRuns.erase(groupRuns.begin(), groupRuns.end());
304     }
305     index++;
306 }
307 
prepareRoundRect()308 void TextLine::prepareRoundRect() {
309     roundRectAttrs.clear();
310     this->iterateThroughVisualRuns(fOwner->paragraphStyle().getTextAlign() == TextAlign::kEnd,
311         [this](const Run* run, SkScalar runOffsetInLine, TextRange textRange, SkScalar* runWidthInLine) {
312             *runWidthInLine = this->iterateThroughSingleRunByStyles(
313             TextAdjustment::GlyphCluster, run, runOffsetInLine, textRange, StyleType::kBackground,
314             [run, this](TextRange textRange, const TextStyle& style, const ClipContext& context) {
315                 roundRectAttrs.push_back({style.getStyleId(), style.getBackgroundRect(), context.clip});
316             });
317             return true;
318         });
319 
320     std::vector<Run*> groupRuns;
321     int index = 0;
322     int preIndex = -1;
323     for (auto& runIndex : fRunsInVisualOrder) {
324         auto run = &this->fOwner->run(runIndex);
325         run->setIndexInLine(static_cast<size_t>(index));
326         computeRoundRect(index, preIndex, groupRuns, run);
327     }
328 }
329 
ensureTextBlobCachePopulated()330 void TextLine::ensureTextBlobCachePopulated() {
331     if (fTextBlobCachePopulated) {
332         return;
333     }
334     if (fBlockRange.width() == 1 &&
335         fRunsInVisualOrder.size() == 1 &&
336         fEllipsis == nullptr &&
337         fOwner->run(fRunsInVisualOrder[0]).placeholderStyle() == nullptr) {
338         if (fClusterRange.width() == 0) {
339             return;
340         }
341         // Most common and most simple case
342         const auto& style = fOwner->block(fBlockRange.start).fStyle;
343         const auto& run = fOwner->run(fRunsInVisualOrder[0]);
344         auto clip = SkRect::MakeXYWH(0.0f, this->sizes().runTop(&run, this->fAscentStyle),
345                                      fAdvance.fX,
346                                      run.calculateHeight(this->fAscentStyle, this->fDescentStyle));
347 
348         auto& start = fOwner->cluster(fClusterRange.start);
349         auto& end = fOwner->cluster(fClusterRange.end - 1);
350         SkASSERT(start.runIndex() == end.runIndex());
351         GlyphRange glyphs;
352         if (run.leftToRight()) {
353             glyphs = GlyphRange(start.startPos(),
354                                 end.isHardBreak() ? end.startPos() : end.endPos());
355         } else {
356             glyphs = GlyphRange(end.startPos(),
357                                 start.isHardBreak() ? start.startPos() : start.endPos());
358         }
359         ClipContext context = {/*run=*/&run,
360                                /*pos=*/glyphs.start,
361                                /*size=*/glyphs.width(),
362                                /*fTextShift=*/-run.positionX(glyphs.start), // starting position
363                                /*clip=*/clip,                               // entire line
364                                /*fExcludedTrailingSpaces=*/0.0f,            // no need for that
365                                /*clippingNeeded=*/false};                   // no need for that
366         this->buildTextBlob(fTextExcludingSpaces, style, context);
367     } else {
368         this->iterateThroughVisualRuns(false,
369            [this](const Run* run,
370                   SkScalar runOffsetInLine,
371                   TextRange textRange,
372                   SkScalar* runWidthInLine) {
373                if (run->placeholderStyle() != nullptr) {
374                    *runWidthInLine = run->advance().fX;
375                    return true;
376                }
377                *runWidthInLine = this->iterateThroughSingleRunByStyles(
378                    TextAdjustment::GlyphCluster,
379                    run,
380                    runOffsetInLine,
381                    textRange,
382                    StyleType::kForeground,
383                    [this](TextRange textRange, const TextStyle& style, const ClipContext& context) {
384                        this->buildTextBlob(textRange, style, context);
385                    });
386                return true;
387            });
388     }
389     fTextBlobCachePopulated = true;
390 }
391 
format(TextAlign align,SkScalar maxWidth,EllipsisModal ellipsisModal)392 void TextLine::format(TextAlign align, SkScalar maxWidth, EllipsisModal ellipsisModal) {
393     SkScalar delta = maxWidth - this->width();
394     if (ellipsisModal == EllipsisModal::HEAD && fEllipsis) {
395         delta += fEllipsis->advance().fX;
396     }
397     if (delta <= 0) {
398         return;
399     }
400 
401     // We do nothing for left align
402     if (align == TextAlign::kJustify) {
403         if (!this->endsWithHardLineBreak()) {
404             this->justify(maxWidth);
405         } else if (fOwner->paragraphStyle().getTextDirection() == TextDirection::kRtl) {
406             // Justify -> Right align
407             fShift = delta;
408         }
409     } else if (align == TextAlign::kRight) {
410         fShift = delta;
411     } else if (align == TextAlign::kCenter) {
412         fShift = delta / 2;
413     }
414 }
415 
scanStyles(StyleType styleType,const RunStyleVisitor & visitor)416 void TextLine::scanStyles(StyleType styleType, const RunStyleVisitor& visitor) {
417     if (this->empty()) {
418         return;
419     }
420 
421     this->iterateThroughVisualRuns(
422             false,
423             [this, visitor, styleType](
424                     const Run* run, SkScalar runOffset, TextRange textRange, SkScalar* width) {
425                 *width = this->iterateThroughSingleRunByStyles(
426                         TextAdjustment::GlyphCluster,
427                         run,
428                         runOffset,
429                         textRange,
430                         styleType,
431                         [visitor](TextRange textRange,
432                                   const TextStyle& style,
433                                   const ClipContext& context) {
434                             visitor(textRange, style, context);
435                         });
436                 return true;
437             });
438 }
439 
extendHeight(const ClipContext & context) const440 SkRect TextLine::extendHeight(const ClipContext& context) const {
441     SkRect result = context.clip;
442     result.fBottom += std::max(this->fMaxRunMetrics.height() - this->height(), 0.0f);
443     return result;
444 }
445 
buildTextBlob(TextRange textRange,const TextStyle & style,const ClipContext & context)446 void TextLine::buildTextBlob(TextRange textRange, const TextStyle& style, const ClipContext& context) {
447     if (context.run->placeholderStyle() != nullptr) {
448         return;
449     }
450 
451     fTextBlobCache.emplace_back();
452     TextBlobRecord& record = fTextBlobCache.back();
453 
454     if (style.hasForeground()) {
455         record.fPaint = style.getForegroundPaintOrID();
456     } else {
457         std::get<SkPaint>(record.fPaint).setColor(style.getColor());
458     }
459     record.fVisitor_Run = context.run;
460     record.fVisitor_Pos = context.pos;
461     record.fVisitor_Size = context.size;
462 
463     // TODO: This is the change for flutter, must be removed later
464 #ifndef USE_SKIA_TXT
465     SkTextBlobBuilder builder;
466 #else
467     RSTextBlobBuilder builder;
468 #endif
469     context.run->copyTo(builder, SkToU32(context.pos), context.size);
470     // when letterspacing < 0, it causes the font is cliped. so the record fClippingNeeded is set false
471     if (context.clippingNeeded) {
472         record.fClipRect = extendHeight(context).makeOffset(this->offset());
473     } else {
474         record.fClipRect = context.clip.makeOffset(this->offset());
475     }
476 
477     SkASSERT(nearlyEqual(context.run->baselineShift(), style.getBaselineShift()));
478     SkScalar correctedBaseline = SkScalarFloorToScalar(this->baseline() + style.getBaselineShift() +  0.5);
479 #ifndef USE_SKIA_TXT
480     record.fBlob = builder.make();
481     if (record.fBlob != nullptr) {
482         record.fBounds.joinPossiblyEmptyRect(record.fBlob->bounds());
483     }
484 #else
485     record.fBlob = builder.Make();
486     if (record.fBlob != nullptr) {
487         auto bounds = record.fBlob->Bounds();
488         if (bounds) {
489             record.fBounds.joinPossiblyEmptyRect(SkRect::MakeLTRB(
490                 bounds->left_, bounds->top_, bounds->right_, bounds->bottom_
491             ));
492         }
493     }
494 #endif
495 
496     record.fOffset = SkPoint::Make(this->offset().fX + context.fTextShift,
497                                    this->offset().fY + correctedBaseline);
498 }
499 
paint(ParagraphPainter * painter,SkScalar x,SkScalar y)500 void TextLine::TextBlobRecord::paint(ParagraphPainter* painter, SkScalar x, SkScalar y) {
501     if (fClippingNeeded) {
502         painter->save();
503         painter->clipRect(fClipRect.makeOffset(x, y));
504     }
505     painter->drawTextBlob(fBlob, x + fOffset.x(), y + fOffset.y(), fPaint);
506     if (fClippingNeeded) {
507         painter->restore();
508     }
509 }
510 
paintBackground(ParagraphPainter * painter,SkScalar x,SkScalar y,TextRange textRange,const TextStyle & style,const ClipContext & context) const511 void TextLine::paintBackground(ParagraphPainter* painter,
512                                SkScalar x,
513                                SkScalar y,
514                                TextRange textRange,
515                                const TextStyle& style,
516                                const ClipContext& context) const {
517     if (style.hasBackground()) {
518         painter->drawRect(context.clip.makeOffset(this->offset() + SkPoint::Make(x, y)),
519                           style.getBackgroundPaintOrID());
520     }
521 }
522 
paintRoundRect(ParagraphPainter * painter,SkScalar x,SkScalar y,const Run * run) const523 void TextLine::paintRoundRect(ParagraphPainter* painter, SkScalar x, SkScalar y, const Run* run) const {
524     size_t index = run->getIndexInLine();
525     if (index >= roundRectAttrs.size()) {
526         return;
527     }
528 
529     const RoundRectAttr& attr = roundRectAttrs[index];
530     if (attr.roundRectStyle.color == 0) {
531         return;
532     }
533 
534     SkScalar ltRadius = 0.0f;
535     SkScalar rtRadius = 0.0f;
536     SkScalar rbRadius = 0.0f;
537     SkScalar lbRadius = 0.0f;
538     RoundRectType rType = run->getRoundRectType();
539     if (rType == RoundRectType::ALL || rType == RoundRectType::LEFT_ONLY) {
540         ltRadius = std::fmin(attr.roundRectStyle.leftTopRadius, run->getMaxRoundRectRadius());
541         lbRadius = std::fmin(attr.roundRectStyle.leftBottomRadius, run->getMaxRoundRectRadius());
542     }
543     if (rType == RoundRectType::ALL || rType == RoundRectType::RIGHT_ONLY) {
544         rtRadius = std::fmin(attr.roundRectStyle.rightTopRadius, run->getMaxRoundRectRadius());
545         rbRadius = std::fmin(attr.roundRectStyle.rightBottomRadius, run->getMaxRoundRectRadius());
546     }
547     const SkVector radii[4] = {{ltRadius, ltRadius}, {rtRadius, rtRadius}, {rbRadius, rbRadius}, {lbRadius, lbRadius}};
548     SkRect skRect(SkRect::MakeLTRB(attr.rect.left(), run->getTopInGroup(), attr.rect.right(),
549         run->getBottomInGroup()));
550     SkRRect skRRect;
551     skRRect.setRectRadii(skRect, radii);
552     skRRect.offset(x + (fOwner->paragraphStyle().getTextAlign() == TextAlign::kEnd ?
553         fShift : fOffset.x()), y + fOffset.y());
554     painter->drawRRect(skRRect, attr.roundRectStyle.color);
555 }
556 
paintShadow(ParagraphPainter * painter,SkScalar x,SkScalar y,TextRange textRange,const TextStyle & style,const ClipContext & context) const557 void TextLine::paintShadow(ParagraphPainter* painter,
558                            SkScalar x,
559                            SkScalar y,
560                            TextRange textRange,
561                            const TextStyle& style,
562                            const ClipContext& context) const {
563     SkScalar correctedBaseline = SkScalarFloorToScalar(this->baseline() + style.getBaselineShift() + 0.5);
564 
565     for (TextShadow shadow : style.getShadows()) {
566         if (!shadow.hasShadow()) continue;
567 
568 #ifndef USE_SKIA_TXT
569         SkTextBlobBuilder builder;
570 #else
571         RSTextBlobBuilder builder;
572 #endif
573         context.run->copyTo(builder, context.pos, context.size);
574 
575         if (context.clippingNeeded) {
576             painter->save();
577             SkRect clip = extendHeight(context);
578             clip.offset(x, y);
579             clip.offset(this->offset());
580             painter->clipRect(clip);
581         }
582 #ifndef USE_SKIA_TXT
583         auto blob = builder.make();
584 #else
585         auto blob = builder.Make();
586 #endif
587         painter->drawTextShadow(blob,
588             x + this->offset().fX + shadow.fOffset.x() + context.fTextShift,
589             y + this->offset().fY + shadow.fOffset.y() + correctedBaseline,
590             shadow.fColor,
591             SkDoubleToScalar(shadow.fBlurSigma));
592         if (context.clippingNeeded) {
593             painter->restore();
594         }
595     }
596 }
597 
paintDecorations(ParagraphPainter * painter,SkScalar x,SkScalar y,TextRange textRange,const TextStyle & style,const ClipContext & context) const598 void TextLine::paintDecorations(ParagraphPainter* painter, SkScalar x, SkScalar y, TextRange textRange, const TextStyle& style, const ClipContext& context) const {
599     ParagraphPainterAutoRestore ppar(painter);
600     painter->translate(x + this->offset().fX, y + this->offset().fY + style.getBaselineShift());
601     Decorations decorations;
602     SkScalar correctedBaseline = SkScalarFloorToScalar(-this->sizes().rawAscent() + style.getBaselineShift() + 0.5);
603     decorations.paint(painter, style, context, correctedBaseline);
604 }
605 
justify(SkScalar maxWidth)606 void TextLine::justify(SkScalar maxWidth) {
607     // Count words and the extra spaces to spread across the line
608     // TODO: do it at the line breaking?..
609     size_t whitespacePatches = 0;
610     SkScalar textLen = 0;
611     bool whitespacePatch = false;
612     this->iterateThroughClustersInGlyphsOrder(false, false,
613         [&whitespacePatches, &textLen, &whitespacePatch](const Cluster* cluster, bool ghost) {
614             if (cluster->isWhitespaceBreak()) {
615                 if (!whitespacePatch) {
616                     whitespacePatch = true;
617                     ++whitespacePatches;
618                 }
619             } else {
620                 whitespacePatch = false;
621             }
622             textLen += cluster->width();
623             return true;
624         });
625 
626     if (whitespacePatches == 0) {
627         return;
628     }
629 
630     SkScalar step = (maxWidth - textLen) / whitespacePatches;
631     SkScalar shift = 0;
632 
633     // Deal with the ghost spaces
634     auto ghostShift = maxWidth - this->fAdvance.fX;
635     // Spread the extra whitespaces
636     whitespacePatch = false;
637     this->iterateThroughClustersInGlyphsOrder(false, true, [&](const Cluster* cluster, bool ghost) {
638 
639         if (ghost) {
640             if (cluster->run().leftToRight()) {
641                 this->shiftCluster(cluster, ghostShift, ghostShift);
642             }
643             return true;
644         }
645 
646         auto prevShift = shift;
647         if (cluster->isWhitespaceBreak()) {
648             if (!whitespacePatch) {
649                 shift += step;
650                 whitespacePatch = true;
651                 --whitespacePatches;
652             }
653         } else {
654             whitespacePatch = false;
655         }
656         shiftCluster(cluster, shift, prevShift);
657         return true;
658     });
659 
660     SkAssertResult(nearlyEqual(shift, maxWidth - textLen));
661     SkASSERT(whitespacePatches == 0);
662 
663     this->fWidthWithSpaces += ghostShift;
664     this->fAdvance.fX = maxWidth;
665 }
666 
shiftCluster(const Cluster * cluster,SkScalar shift,SkScalar prevShift)667 void TextLine::shiftCluster(const Cluster* cluster, SkScalar shift, SkScalar prevShift) {
668 
669     auto& run = cluster->run();
670     auto start = cluster->startPos();
671     auto end = cluster->endPos();
672 
673     if (end == run.size()) {
674         // Set the same shift for the fake last glyph (to avoid all extra checks)
675         ++end;
676     }
677 
678     if (run.fJustificationShifts.empty()) {
679         // Do not fill this array until needed
680         run.fJustificationShifts.push_back_n(run.size() + 1, { 0, 0 });
681     }
682 
683     for (size_t pos = start; pos < end; ++pos) {
684         run.fJustificationShifts[pos] = { shift, prevShift };
685     }
686 }
687 
createTailEllipsis(SkScalar maxWidth,const SkString & ellipsis,bool ltr,WordBreakType wordBreakType)688 void TextLine::createTailEllipsis(SkScalar maxWidth, const SkString& ellipsis, bool ltr, WordBreakType wordBreakType) {
689     // Replace some clusters with the ellipsis
690     // Go through the clusters in the reverse logical order
691     // taking off cluster by cluster until the ellipsis fits
692     SkScalar width = fAdvance.fX;
693     RunIndex lastRun = EMPTY_RUN;
694     std::unique_ptr<Run> ellipsisRun;
695     int wordCount = 0;
696     bool inWord = false;
697 
698     for (auto clusterIndex = fGhostClusterRange.start; clusterIndex < fGhostClusterRange.end; ++clusterIndex) {
699         auto& cluster = fOwner->cluster(clusterIndex);
700         if (cluster.isWordBreak()) {
701             inWord = false;
702         } else if (!inWord) {
703             ++wordCount;
704             inWord = true;
705         }
706     }
707 
708     bool iterForWord = false;
709     for (auto clusterIndex = fGhostClusterRange.end; clusterIndex > fGhostClusterRange.start; --clusterIndex) {
710         auto& cluster = fOwner->cluster(clusterIndex - 1);
711         // Shape the ellipsis if the run has changed
712         if (lastRun != cluster.runIndex()) {
713             ellipsisRun = this->shapeEllipsis(ellipsis, &cluster);
714             // We may need to continue
715             lastRun = cluster.runIndex();
716         }
717 
718         if (!cluster.isWordBreak()) {
719             inWord = true;
720         } else if (inWord) {
721             --wordCount;
722             inWord = false;
723         }
724         // See if it fits
725         if (width + ellipsisRun->advance().fX > maxWidth) {
726             width -= cluster.width();
727             // Continue if the ellipsis does not fit
728             iterForWord = (wordCount != 1 && wordBreakType != WordBreakType::BREAK_ALL && !cluster.isWordBreak());
729             if (std::floor(width) > 0) {
730                 continue;
731             }
732         }
733 
734         if (iterForWord && !cluster.isWordBreak()) {
735             width -= cluster.width();
736             if (std::floor(width) > 0) {
737                 continue;
738             }
739         }
740 
741         // We found enough room for the ellipsis
742         fAdvance.fX = width;
743         fEllipsis = std::move(ellipsisRun);
744         fEllipsis->setOwner(fOwner);
745         fTextRangeReplacedByEllipsis = TextRange(cluster.textRange().end, fOwner->text().size());
746 
747         // Let's update the line
748         fClusterRange.end = clusterIndex;
749         fGhostClusterRange.end = fClusterRange.end;
750         fEllipsis->fClusterStart = cluster.textRange().start;
751         fText.end = cluster.textRange().end;
752         fTextIncludingNewlines.end = cluster.textRange().end;
753         fTextExcludingSpaces.end = cluster.textRange().end;
754         break;
755     }
756 
757     if (!fEllipsis) {
758         // Weird situation: ellipsis does not fit; no ellipsis then
759         fClusterRange.end = fClusterRange.start;
760         fGhostClusterRange.end = fClusterRange.start;
761         fText.end = fText.start;
762         fTextIncludingNewlines.end = fTextIncludingNewlines.start;
763         fTextExcludingSpaces.end = fTextExcludingSpaces.start;
764         fAdvance.fX = 0;
765     }
766 }
767 
createHeadEllipsis(SkScalar maxWidth,const SkString & ellipsis,bool)768 void TextLine::createHeadEllipsis(SkScalar maxWidth, const SkString& ellipsis, bool) {
769     if (fAdvance.fX <= maxWidth) {
770         return;
771     }
772     SkScalar width = fAdvance.fX;
773     std::unique_ptr<Run> ellipsisRun;
774     RunIndex lastRun = EMPTY_RUN;
775     for (auto clusterIndex = fGhostClusterRange.start; clusterIndex < fGhostClusterRange.end; ++clusterIndex) {
776         auto& cluster = fOwner->cluster(clusterIndex);
777         // Shape the ellipsis if the run has changed
778         if (lastRun != cluster.runIndex()) {
779             ellipsisRun = this->shapeEllipsis(ellipsis, &cluster);
780             // We may need to continue
781             lastRun = cluster.runIndex();
782         }
783         // See if it fits
784         if (width + ellipsisRun->advance().fX > maxWidth) {
785             width -= cluster.width();
786             // Continue if the ellipsis does not fit
787             if (std::floor(width) > 0) {
788                 continue;
789             }
790         }
791         // We found enough room for the ellipsis
792         fAdvance.fX = width + ellipsisRun->advance().fX;
793         fEllipsis = std::move(ellipsisRun);
794         fEllipsis->setOwner(fOwner);
795         fTextRangeReplacedByEllipsis = TextRange(0, cluster.textRange().start);
796         fClusterRange.start = clusterIndex;
797         fGhostClusterRange.start = fClusterRange.start;
798         fEllipsis->fClusterStart = cluster.textRange().start;
799         fText.start = cluster.textRange().start;
800         fTextIncludingNewlines.start = cluster.textRange().start;
801         fTextExcludingSpaces.start = cluster.textRange().start;
802         break;
803     }
804 
805     if (!fEllipsis) {
806         // Weird situation: ellipsis does not fit; no ellipsis then
807         fClusterRange.start = fClusterRange.end;
808         fGhostClusterRange.start = fClusterRange.end;
809         fText.start = fText.end;
810         fTextIncludingNewlines.start = fTextIncludingNewlines.end;
811         fTextExcludingSpaces.start = fTextExcludingSpaces.end;
812         fAdvance.fX = 0;
813     }
814 }
815 
nextUtf8Unit(const char ** ptr,const char * end)816 static inline SkUnichar nextUtf8Unit(const char** ptr, const char* end) {
817     SkUnichar val = SkUTF::NextUTF8(ptr, end);
818     return val < 0 ? 0xFFFD : val;
819 }
820 
shapeEllipsis(const SkString & ellipsis,const Cluster * cluster)821 std::unique_ptr<Run> TextLine::shapeEllipsis(const SkString& ellipsis, const Cluster* cluster) {
822 
823     class ShapeHandler final : public SkShaper::RunHandler {
824     public:
825         ShapeHandler(SkScalar lineHeight, bool useHalfLeading, SkScalar baselineShift, const SkString& ellipsis)
826             : fRun(nullptr), fLineHeight(lineHeight), fUseHalfLeading(useHalfLeading), fBaselineShift(baselineShift), fEllipsis(ellipsis) {}
827         std::unique_ptr<Run> run() & { return std::move(fRun); }
828 
829     private:
830         void beginLine() override {}
831 
832         void runInfo(const RunInfo&) override {}
833 
834         void commitRunInfo() override {}
835 
836         Buffer runBuffer(const RunInfo& info) override {
837             SkASSERT(!fRun);
838             fRun = std::make_unique<Run>(nullptr, info, 0, fLineHeight, fUseHalfLeading, fBaselineShift, 0, 0);
839             return fRun->newRunBuffer();
840         }
841 
842         void commitRunBuffer(const RunInfo& info) override {
843             fRun->fAdvance.fX = info.fAdvance.fX;
844             fRun->fAdvance.fY = fRun->advance().fY;
845             fRun->fPlaceholderIndex = std::numeric_limits<size_t>::max();
846             fRun->fEllipsis = true;
847         }
848 
849         void commitLine() override {}
850 
851         std::unique_ptr<Run> fRun;
852         SkScalar fLineHeight;
853         bool fUseHalfLeading;
854         SkScalar fBaselineShift;
855         SkString fEllipsis;
856     };
857 
858     const Run& run = cluster->run();
859     TextStyle textStyle = fOwner->paragraphStyle().getTextStyle();
860     for (auto i = fBlockRange.start; i < fBlockRange.end; ++i) {
861         auto& block = fOwner->block(i);
862         if (run.leftToRight() && cluster->textRange().end <= block.fRange.end) {
863             textStyle = block.fStyle;
864             break;
865         } else if (!run.leftToRight() && cluster->textRange().start <= block.fRange.end) {
866             textStyle = block.fStyle;
867             break;
868         }
869     }
870 
871 #ifndef USE_SKIA_TXT
872     auto shaped = [&](sk_sp<SkTypeface> typeface, bool fallback) -> std::unique_ptr<Run> {
873 #else
874     auto shaped = [&](std::shared_ptr<RSTypeface> typeface, bool fallback) -> std::unique_ptr<Run> {
875 #endif
876         ShapeHandler handler(run.heightMultiplier(), run.useHalfLeading(), run.baselineShift(), ellipsis);
877 #ifndef USE_SKIA_TXT
878         SkFont font(typeface, textStyle.getFontSize());
879         font.setEdging(SkFont::Edging::kAntiAlias);
880         font.setHinting(SkFontHinting::kSlight);
881         font.setSubpixel(true);
882 #else
883         RSFont font(typeface, textStyle.getFontSize(), 1, 0);
884         font.SetEdging(RSDrawing::FontEdging::ANTI_ALIAS);
885         font.SetHinting(RSDrawing::FontHinting::SLIGHT);
886         font.SetSubpixel(true);
887 #endif
888 
889 #ifndef USE_SKIA_TXT
890         std::unique_ptr<SkShaper> shaper = SkShaper::MakeShapeDontWrapOrReorder(
891                             fOwner->getUnicode()->copy(),
892                             fallback ? SkFontMgr::RefDefault() : SkFontMgr::RefEmpty());
893 #else
894         std::unique_ptr<SkShaper> shaper = SkShaper::MakeShapeDontWrapOrReorder(
895                             fOwner->getUnicode()->copy(),
896                             fallback ? RSFontMgr::CreateDefaultFontMgr() : RSFontMgr::CreateDefaultFontMgr());
897 #endif
898         shaper->shape(ellipsis.c_str(),
899                       ellipsis.size(),
900                       font,
901                       true,
902                       std::numeric_limits<SkScalar>::max(),
903                       &handler);
904         auto ellipsisRun = handler.run();
905         ellipsisRun->fTextRange = TextRange(0, ellipsis.size());
906         ellipsisRun->fOwner = fOwner;
907         return ellipsisRun;
908     };
909 
910     // Check all allowed fonts
911     auto typefaces = fOwner->fontCollection()->findTypefaces(
912             textStyle.getFontFamilies(), textStyle.getFontStyle(), textStyle.getFontArguments());
913     for (const auto& typeface : typefaces) {
914         auto ellipsisRun = shaped(typeface, false);
915         if (ellipsisRun->isResolved()) {
916             return ellipsisRun;
917         }
918     }
919 
920     // Try the fallback
921     if (fOwner->fontCollection()->fontFallbackEnabled()) {
922         const char* ch = ellipsis.c_str();
923         SkUnichar unicode = nextUtf8Unit(&ch, ellipsis.c_str() + ellipsis.size());
924 
925         auto typeface = fOwner->fontCollection()->defaultFallback(
926             unicode, textStyle.getFontStyle(), textStyle.getLocale());
927         if (typeface) {
928             if (textStyle.getFontArguments()) {
929                 typeface = fOwner->fontCollection()->CloneTypeface(typeface, textStyle.getFontArguments());
930             }
931             auto ellipsisRun = shaped(typeface, true);
932             if (ellipsisRun->isResolved()) {
933                 return ellipsisRun;
934             }
935         }
936     }
937 
938     // Check the current font
939 #ifndef USE_SKIA_TXT
940     auto ellipsisRun = shaped(run.fFont.refTypeface(), false);
941 #else
942     auto ellipsisRun = shaped(const_cast<RSFont&>(run.fFont).GetTypeface(), false);
943 #endif
944     if (ellipsisRun->isResolved()) {
945         return ellipsisRun;
946     }
947     return ellipsisRun;
948 }
949 
950 TextLine::ClipContext TextLine::measureTextInsideOneRun(TextRange textRange,
951                                                         const Run* run,
952                                                         SkScalar runOffsetInLine,
953                                                         SkScalar textOffsetInRunInLine,
954                                                         bool includeGhostSpaces,
955                                                         TextAdjustment textAdjustment) const {
956     ClipContext result = { run, 0, run->size(), 0, SkRect::MakeEmpty(), 0, false };
957 
958     if (run->fEllipsis) {
959         // Both ellipsis and placeholders can only be measured as one glyph
960         result.fTextShift = runOffsetInLine;
961         result.clip = SkRect::MakeXYWH(runOffsetInLine,
962                                        sizes().runTop(run, this->fAscentStyle),
963                                        run->advance().fX,
964                                        run->calculateHeight(this->fAscentStyle,this->fDescentStyle));
965         return result;
966     } else if (run->isPlaceholder()) {
967         result.fTextShift = runOffsetInLine;
968         if (SkScalarIsFinite(run->fFontMetrics.fAscent)) {
969           result.clip = SkRect::MakeXYWH(runOffsetInLine,
970                                          sizes().runTop(run, this->fAscentStyle),
971                                          run->advance().fX,
972                                          run->calculateHeight(this->fAscentStyle,this->fDescentStyle));
973         } else {
974             result.clip = SkRect::MakeXYWH(runOffsetInLine, run->fFontMetrics.fAscent, run->advance().fX, 0);
975         }
976         return result;
977     } else if (textRange.empty()) {
978         return result;
979     }
980 
981     TextRange originalTextRange(textRange); // We need it for proportional measurement
982     // Find [start:end] clusters for the text
983     while (true) {
984         // Update textRange by cluster edges (shift start up to the edge of the cluster)
985         // TODO: remove this limitation?
986         TextRange updatedTextRange;
987         bool found;
988         std::tie(found, updatedTextRange.start, updatedTextRange.end) =
989                                         run->findLimitingGlyphClusters(textRange);
990         if (!found) {
991             return result;
992         }
993 
994         if ((textAdjustment & TextAdjustment::Grapheme) == 0) {
995             textRange = updatedTextRange;
996             break;
997         }
998 
999         // Update text range by grapheme edges (shift start up to the edge of the grapheme)
1000         std::tie(found, updatedTextRange.start, updatedTextRange.end) =
1001                                     run->findLimitingGraphemes(updatedTextRange);
1002         if (updatedTextRange == textRange) {
1003             break;
1004         }
1005 
1006         // Some clusters are inside graphemes and we need to adjust them
1007         //SkDebugf("Correct range: [%d:%d) -> [%d:%d)\n", textRange.start, textRange.end, startIndex, endIndex);
1008         textRange = updatedTextRange;
1009 
1010         // Move the start until it's on the grapheme edge (and glypheme, too)
1011     }
1012     Cluster* start = &fOwner->cluster(fOwner->clusterIndex(textRange.start));
1013     Cluster* end = &fOwner->cluster(fOwner->clusterIndex(textRange.end - (textRange.width() == 0 ? 0 : 1)));
1014 
1015     if (!run->leftToRight()) {
1016         std::swap(start, end);
1017     }
1018     result.pos = start->startPos();
1019     result.size = (end->isHardBreak() ? end->startPos() : end->endPos()) - start->startPos();
1020     auto textStartInRun = run->positionX(start->startPos());
1021     auto textStartInLine = runOffsetInLine + textOffsetInRunInLine;
1022     if (!run->leftToRight()) {
1023         std::swap(start, end);
1024     }
1025 /*
1026     if (!run->fJustificationShifts.empty()) {
1027         SkDebugf("Justification for [%d:%d)\n", textRange.start, textRange.end);
1028         for (auto i = result.pos; i < result.pos + result.size; ++i) {
1029             auto j = run->fJustificationShifts[i];
1030             SkDebugf("[%d] = %f %f\n", i, j.fX, j.fY);
1031         }
1032     }
1033 */
1034     // Calculate the clipping rectangle for the text with cluster edges
1035     // There are 2 cases:
1036     // EOL (when we expect the last cluster clipped without any spaces)
1037     // Anything else (when we want the cluster width contain all the spaces -
1038     // coming from letter spacing or word spacing or justification)
1039     result.clip =
1040             SkRect::MakeXYWH(0,
1041                              sizes().runTop(run, this->fAscentStyle),
1042                              run->calculateWidth(result.pos, result.pos + result.size, false),
1043                              run->calculateHeight(this->fAscentStyle,this->fDescentStyle));
1044 
1045     // Correct the width in case the text edges don't match clusters
1046     // TODO: This is where we get smart about selecting a part of a cluster
1047     //  by shaping each grapheme separately and then use the result sizes
1048     //  to calculate the proportions
1049     auto leftCorrection = start->sizeToChar(originalTextRange.start);
1050     auto rightCorrection = end->sizeFromChar(originalTextRange.end - 1);
1051     /*
1052     SkDebugf("[%d: %d) => [%d: %d), @%d, %d: [%f:%f) + [%f:%f) = ", // جَآَهُ
1053              originalTextRange.start, originalTextRange.end, textRange.start, textRange.end,
1054              result.pos, result.size,
1055              result.clip.fLeft, result.clip.fRight, leftCorrection, rightCorrection);
1056      */
1057     result.clippingNeeded = leftCorrection != 0 || rightCorrection != 0;
1058     if (run->leftToRight()) {
1059         result.clip.fLeft += leftCorrection;
1060         result.clip.fRight -= rightCorrection;
1061         textStartInLine -= leftCorrection;
1062     } else {
1063         result.clip.fRight -= leftCorrection;
1064         result.clip.fLeft += rightCorrection;
1065         textStartInLine -= rightCorrection;
1066     }
1067 
1068     result.clip.offset(textStartInLine, 0);
1069     //SkDebugf("@%f[%f:%f)\n", textStartInLine, result.clip.fLeft, result.clip.fRight);
1070 
1071     if (compareRound(result.clip.fRight, fAdvance.fX, fOwner->getApplyRoundingHack()) > 0 && !includeGhostSpaces) {
1072         // There are few cases when we need it.
1073         // The most important one: we measure the text with spaces at the end (or at the beginning in RTL)
1074         // and we should ignore these spaces
1075         if (fOwner->paragraphStyle().getTextDirection() == TextDirection::kLtr) {
1076             // We only use this member for LTR
1077             result.fExcludedTrailingSpaces = std::max(result.clip.fRight - fAdvance.fX, 0.0f);
1078             result.clippingNeeded = true;
1079             result.clip.fRight = fOwner->paragraphStyle().getTextAlign() == TextAlign::kEnd ?
1080                 fWidthWithSpaces : fAdvance.fX;
1081         }
1082     }
1083 
1084     if (result.clip.width() < 0) {
1085         // Weird situation when glyph offsets move the glyph to the left
1086         // (happens with zalgo texts, for instance)
1087         result.clip.fRight = result.clip.fLeft;
1088     }
1089 
1090     // The text must be aligned with the lineOffset
1091     result.fTextShift = textStartInLine - textStartInRun;
1092 
1093     return result;
1094 }
1095 
1096 void TextLine::iterateThroughClustersInGlyphsOrder(bool reversed,
1097                                                    bool includeGhosts,
1098                                                    const ClustersVisitor& visitor) const {
1099     // Walk through the clusters in the logical order (or reverse)
1100     SkSpan<const size_t> runs(fRunsInVisualOrder.data(), fRunsInVisualOrder.size());
1101     bool ignore = false;
1102     directional_for_each(runs, !reversed, [&](decltype(runs[0]) r) {
1103         if (ignore) return;
1104         auto run = this->fOwner->run(r);
1105         auto trimmedRange = fClusterRange.intersection(run.clusterRange());
1106         auto trailedRange = fGhostClusterRange.intersection(run.clusterRange());
1107         SkASSERT(trimmedRange.start == trailedRange.start);
1108 
1109         auto trailed = fOwner->clusters(trailedRange);
1110         auto trimmed = fOwner->clusters(trimmedRange);
1111         directional_for_each(trailed, reversed != run.leftToRight(), [&](Cluster& cluster) {
1112             if (ignore) return;
1113             bool ghost =  &cluster >= trimmed.end();
1114             if (!includeGhosts && ghost) {
1115                 return;
1116             }
1117             if (!visitor(&cluster, ghost)) {
1118                 ignore = true;
1119                 return;
1120             }
1121         });
1122     });
1123 }
1124 
1125 SkScalar TextLine::iterateThroughSingleRunByStyles(TextAdjustment textAdjustment,
1126                                                    const Run* run,
1127                                                    SkScalar runOffset,
1128                                                    TextRange textRange,
1129                                                    StyleType styleType,
1130                                                    const RunStyleVisitor& visitor) const {
1131     auto includeGhostSpaces = (styleType == StyleType::kDecorations || styleType == StyleType::kNone);
1132     auto correctContext = [&](TextRange textRange, SkScalar textOffsetInRun) -> ClipContext {
1133         auto result = this->measureTextInsideOneRun(
1134             textRange, run, runOffset, textOffsetInRun, includeGhostSpaces, textAdjustment);
1135         if (styleType == StyleType::kDecorations) {
1136             // Decorations are drawn based on the real font metrics (regardless of styles and strut)
1137             result.clip.fTop = this->sizes().runTop(run, LineMetricStyle::CSS) - run->baselineShift();
1138             result.clip.fBottom = result.clip.fTop +
1139                                   run->calculateHeight(LineMetricStyle::CSS, LineMetricStyle::CSS);
1140         }
1141         return result;
1142     };
1143 
1144     if (run->fEllipsis) {
1145         // Extra efforts to get the ellipsis text style
1146         ClipContext clipContext = correctContext(run->textRange(), 0.0f);
1147         for (BlockIndex index = fBlockRange.start; index < fBlockRange.end; ++index) {
1148            auto block = fOwner->styles().begin() + index;
1149            if (block->fRange.start >= run->fClusterStart && block->fRange.end < run->fClusterStart) {
1150                visitor(fTextRangeReplacedByEllipsis, block->fStyle, clipContext);
1151                return run->advance().fX;
1152            }
1153         }
1154         SkASSERT(false);
1155     }
1156 
1157     if (styleType == StyleType::kNone) {
1158         ClipContext clipContext = correctContext(textRange, 0.0f);
1159         if (clipContext.clip.height() > 0) {
1160             visitor(textRange, TextStyle(), clipContext);
1161             return clipContext.clip.width();
1162         } else {
1163             return 0;
1164         }
1165     }
1166 
1167     TextIndex start = EMPTY_INDEX;
1168     size_t size = 0;
1169     const TextStyle* prevStyle = nullptr;
1170     SkScalar textOffsetInRun = 0;
1171 
1172     const BlockIndex blockRangeSize = fBlockRange.end - fBlockRange.start;
1173     for (BlockIndex index = 0; index <= blockRangeSize; ++index) {
1174 
1175         TextRange intersect;
1176         TextStyle* style = nullptr;
1177         if (index < blockRangeSize) {
1178             auto block = fOwner->styles().begin() +
1179                  (run->leftToRight() ? fBlockRange.start + index : fBlockRange.end - index - 1);
1180 
1181             // Get the text
1182             intersect = intersected(block->fRange, textRange);
1183             if (intersect.width() == 0) {
1184                 if (start == EMPTY_INDEX) {
1185                     // This style is not applicable to the text yet
1186                     continue;
1187                 } else {
1188                     // We have found all the good styles already
1189                     // but we need to process the last one of them
1190                     intersect = TextRange(start, start + size);
1191                     index = fBlockRange.end;
1192                 }
1193             } else {
1194                 // Get the style
1195                 style = &block->fStyle;
1196                 if (start != EMPTY_INDEX && style->matchOneAttribute(styleType, *prevStyle)) {
1197                     size += intersect.width();
1198                     // RTL text intervals move backward
1199                     start = std::min(intersect.start, start);
1200                     continue;
1201                 } else if (start == EMPTY_INDEX ) {
1202                     // First time only
1203                     prevStyle = style;
1204                     size = intersect.width();
1205                     start = intersect.start;
1206                     continue;
1207                 }
1208             }
1209         } else if (prevStyle != nullptr) {
1210             // This is the last style
1211         } else {
1212             break;
1213         }
1214 
1215         // We have the style and the text
1216         auto runStyleTextRange = TextRange(start, start + size);
1217         ClipContext clipContext = correctContext(runStyleTextRange, textOffsetInRun);
1218         textOffsetInRun += clipContext.clip.width();
1219         if (clipContext.clip.height() == 0) {
1220             continue;
1221         }
1222 
1223         RectStyle temp;
1224         if (styleType == StyleType::kBackground &&
1225             prevStyle->getBackgroundRect() != temp &&
1226             prevStyle->getHeight() != 0) {
1227                 clipContext.clip.fTop = run->fFontMetrics.fAscent - run->fCorrectAscent;
1228                 clipContext.clip.fBottom = clipContext.clip.fTop + run->fFontMetrics.fDescent -
1229                     run->fFontMetrics.fAscent;
1230         }
1231         visitor(runStyleTextRange, *prevStyle, clipContext);
1232 
1233         // Start all over again
1234         prevStyle = style;
1235         start = intersect.start;
1236         size = intersect.width();
1237     }
1238     return textOffsetInRun;
1239 }
1240 
1241 void TextLine::iterateThroughVisualRuns(bool includingGhostSpaces, const RunVisitor& visitor) const {
1242 
1243     // Walk through all the runs that intersect with the line in visual order
1244     SkScalar width = 0;
1245     SkScalar runOffset = 0;
1246     SkScalar totalWidth = 0;
1247     bool ellipsisModeIsHead = fOwner->paragraphStyle().getEllipsisMod() == EllipsisModal::HEAD;
1248     bool isAlreadyUseEllipsis = false;
1249     auto textRange = includingGhostSpaces ? this->textWithNewlines() : this->trimmedText();
1250 
1251     if (fOwner->paragraphStyle().getTextDirection() == TextDirection::kRtl &&
1252         this->ellipsis() != nullptr && !ellipsisModeIsHead) {
1253         isAlreadyUseEllipsis = true;
1254         runOffset = this->ellipsis()->offset().fX;
1255         if (visitor(ellipsis(), runOffset, fTextRangeReplacedByEllipsis, &width)) {
1256         }
1257     } else if (ellipsisModeIsHead && this->ellipsis() != nullptr &&
1258         fOwner->paragraphStyle().getTextDirection() == TextDirection::kLtr) {
1259         isAlreadyUseEllipsis = true;
1260         runOffset = this->ellipsis()->offset().fX;
1261         if (visitor(ellipsis(), runOffset, fTextRangeReplacedByEllipsis, &width)) {
1262         }
1263     }
1264 
1265     for (auto& runIndex : fRunsInVisualOrder) {
1266 
1267         const auto run = &this->fOwner->run(runIndex);
1268         auto lineIntersection = intersected(run->textRange(), textRange);
1269         if (lineIntersection.width() == 0 && this->width() != 0) {
1270             // TODO: deal with empty runs in a better way
1271             continue;
1272         }
1273         if (!run->leftToRight() && runOffset == 0 && includingGhostSpaces) {
1274             // runOffset does not take in account a possibility
1275             // that RTL run could start before the line (trailing spaces)
1276             // so we need to do runOffset -= "trailing whitespaces length"
1277             TextRange whitespaces = intersected(
1278                     TextRange(fTextExcludingSpaces.end, fTextIncludingNewlines.end), run->fTextRange);
1279             if (whitespaces.width() > 0) {
1280                 auto whitespacesLen = measureTextInsideOneRun(whitespaces, run, runOffset, 0, true, TextAdjustment::GlyphCluster).clip.width();
1281                 runOffset -= whitespacesLen;
1282             }
1283         }
1284         runOffset += width;
1285         totalWidth += width;
1286         if (!visitor(run, runOffset, lineIntersection, &width)) {
1287             return;
1288         }
1289     }
1290 
1291     runOffset += width;
1292     totalWidth += width;
1293 
1294     if (this->ellipsis() != nullptr && !isAlreadyUseEllipsis) {
1295         if (visitor(ellipsis(), runOffset, fTextRangeReplacedByEllipsis, &width)) {
1296             totalWidth += width;
1297         }
1298     }
1299 
1300     if (!includingGhostSpaces && compareRound(totalWidth, this->width(), fOwner->getApplyRoundingHack()) != 0) {
1301     // This is a very important assert!
1302     // It asserts that 2 different ways of calculation come with the same results
1303         SkDebugf("ASSERT: %f != %f\n", totalWidth, this->width());
1304         SkASSERT(false);
1305     }
1306 }
1307 
1308 SkVector TextLine::offset() const {
1309     return fOffset + SkVector::Make(fShift, 0);
1310 }
1311 
1312 LineMetrics TextLine::getMetrics() const {
1313     LineMetrics result;
1314 
1315     // Fill out the metrics
1316     fOwner->ensureUTF16Mapping();
1317     result.fStartIndex = fOwner->getUTF16Index(fTextExcludingSpaces.start);
1318     result.fEndExcludingWhitespaces = fOwner->getUTF16Index(fTextExcludingSpaces.end);
1319     result.fEndIndex = fOwner->getUTF16Index(fText.end);
1320     result.fEndIncludingNewline = fOwner->getUTF16Index(fTextIncludingNewlines.end);
1321     result.fHardBreak = endsWithHardLineBreak();
1322     result.fAscent = - fMaxRunMetrics.ascent();
1323     result.fDescent = fMaxRunMetrics.descent();
1324     result.fUnscaledAscent = - fMaxRunMetrics.ascent(); // TODO: implement
1325     result.fHeight = fAdvance.fY;
1326     result.fWidth = fAdvance.fX;
1327     if (fOwner->getApplyRoundingHack()) {
1328         result.fHeight = littleRound(result.fHeight);
1329         result.fWidth = littleRound(result.fWidth);
1330     }
1331     result.fLeft = this->offset().fX;
1332     // This is Flutter definition of a baseline
1333     result.fBaseline = this->offset().fY + this->height() - this->sizes().descent();
1334     result.fLineNumber = this - fOwner->lines().begin();
1335     result.fWidthWithSpaces = fWidthWithSpaces;
1336     result.fTopHeight = this->offset().fY;
1337 
1338     // Fill out the style parts
1339     this->iterateThroughVisualRuns(false,
1340         [this, &result]
1341         (const Run* run, SkScalar runOffsetInLine, TextRange textRange, SkScalar* runWidthInLine) {
1342         if (run->placeholderStyle() != nullptr) {
1343             *runWidthInLine = run->advance().fX;
1344             return true;
1345         }
1346         *runWidthInLine = this->iterateThroughSingleRunByStyles(
1347         TextAdjustment::GlyphCluster, run, runOffsetInLine, textRange, StyleType::kForeground,
1348         [&result, &run](TextRange textRange, const TextStyle& style, const ClipContext& context) {
1349 #ifndef USE_SKIA_TXT
1350             SkFontMetrics fontMetrics;
1351             run->fFont.getMetrics(&fontMetrics);
1352 #else
1353             RSFontMetrics fontMetrics;
1354             run->fFont.GetMetrics(&fontMetrics);
1355 #endif
1356             StyleMetrics styleMetrics(&style, fontMetrics);
1357             result.fLineMetrics.emplace(textRange.start, styleMetrics);
1358         });
1359         return true;
1360     });
1361 
1362     return result;
1363 }
1364 
1365 bool TextLine::isFirstLine() const {
1366     return this == &fOwner->lines().front();
1367 }
1368 
1369 bool TextLine::isLastLine() const {
1370     return this == &fOwner->lines().back();
1371 }
1372 
1373 bool TextLine::endsWithHardLineBreak() const {
1374     // TODO: For some reason Flutter imagines a hard line break at the end of the last line.
1375     //  To be removed...
1376     return (fGhostClusterRange.width() > 0 && fOwner->cluster(fGhostClusterRange.end - 1).isHardBreak()) ||
1377            fEllipsis != nullptr ||
1378            fGhostClusterRange.end == fOwner->clusters().size() - 1;
1379 }
1380 
1381 void TextLine::getRectsForRange(TextRange textRange0,
1382                                 RectHeightStyle rectHeightStyle,
1383                                 RectWidthStyle rectWidthStyle,
1384                                 std::vector<TextBox>& boxes) const
1385 {
1386     const Run* lastRun = nullptr;
1387     auto startBox = boxes.size();
1388     this->iterateThroughVisualRuns(true,
1389         [textRange0, rectHeightStyle, rectWidthStyle, &boxes, &lastRun, startBox, this]
1390         (const Run* run, SkScalar runOffsetInLine, TextRange textRange, SkScalar* runWidthInLine) {
1391         *runWidthInLine = this->iterateThroughSingleRunByStyles(
1392         TextAdjustment::GraphemeGluster, run, runOffsetInLine, textRange, StyleType::kNone,
1393         [run, runOffsetInLine, textRange0, rectHeightStyle, rectWidthStyle, &boxes, &lastRun, startBox, this]
1394         (TextRange textRange, const TextStyle& style, const TextLine::ClipContext& lineContext) {
1395 
1396             auto intersect = textRange * textRange0;
1397             if (intersect.empty()) {
1398                 return true;
1399             }
1400 
1401             auto paragraphStyle = fOwner->paragraphStyle();
1402 
1403             // Found a run that intersects with the text
1404             auto context = this->measureTextInsideOneRun(
1405                     intersect, run, runOffsetInLine, 0, true, TextAdjustment::GraphemeGluster);
1406             SkRect clip = context.clip;
1407             clip.offset(lineContext.fTextShift - context.fTextShift, 0);
1408 
1409             switch (rectHeightStyle) {
1410                 case RectHeightStyle::kMax:
1411                     // TODO: Change it once flutter rolls into google3
1412                     //  (probably will break things if changed before)
1413                     clip.fBottom = this->height();
1414                     clip.fTop = this->sizes().delta();
1415                     break;
1416                 case RectHeightStyle::kIncludeLineSpacingTop: {
1417                     clip.fBottom = this->height();
1418                     clip.fTop = this->sizes().delta();
1419                     auto verticalShift = this->sizes().rawAscent() - this->sizes().ascent();
1420                     if (isFirstLine()) {
1421                         clip.fTop += verticalShift;
1422                     }
1423                     break;
1424                 }
1425                 case RectHeightStyle::kIncludeLineSpacingMiddle: {
1426                     clip.fBottom = this->height();
1427                     clip.fTop = this->sizes().delta();
1428                     auto verticalShift = this->sizes().rawAscent() - this->sizes().ascent();
1429                     clip.offset(0, verticalShift / 2.0);
1430                     if (isFirstLine()) {
1431                         clip.fTop += verticalShift / 2.0;
1432                     }
1433                     if (isLastLine()) {
1434                         clip.fBottom -= verticalShift / 2.0;
1435                     }
1436                     break;
1437                  }
1438                 case RectHeightStyle::kIncludeLineSpacingBottom: {
1439                     clip.fBottom = this->height();
1440                     clip.fTop = this->sizes().delta();
1441                     auto verticalShift = this->sizes().rawAscent() - this->sizes().ascent();
1442                     clip.offset(0, verticalShift);
1443                     if (isLastLine()) {
1444                         clip.fBottom -= verticalShift;
1445                     }
1446                     break;
1447                 }
1448                 case RectHeightStyle::kStrut: {
1449                     const auto& strutStyle = paragraphStyle.getStrutStyle();
1450                     if (strutStyle.getStrutEnabled()
1451                         && strutStyle.getFontSize() > 0) {
1452                         auto strutMetrics = fOwner->strutMetrics();
1453                         auto top = this->baseline();
1454                         clip.fTop = top + strutMetrics.ascent();
1455                         clip.fBottom = top + strutMetrics.descent();
1456                     }
1457                 }
1458                 break;
1459                 case RectHeightStyle::kTight: {
1460                     if (run->fHeightMultiplier <= 0) {
1461                         break;
1462                     }
1463                     const auto effectiveBaseline = this->baseline() + this->sizes().delta();
1464                     clip.fTop = effectiveBaseline + run->ascent();
1465                     clip.fBottom = effectiveBaseline + run->descent();
1466                 }
1467                 break;
1468                 default:
1469                     SkASSERT(false);
1470                 break;
1471             }
1472 
1473             // Separate trailing spaces and move them in the default order of the paragraph
1474             // in case the run order and the paragraph order don't match
1475             SkRect trailingSpaces = SkRect::MakeEmpty();
1476             if (this->trimmedText().end <this->textWithNewlines().end && // Line has trailing space
1477                 this->textWithNewlines().end == intersect.end &&         // Range is at the end of the line
1478                 this->trimmedText().end > intersect.start)               // Range has more than just spaces
1479             {
1480                 auto delta = this->spacesWidth();
1481                 trailingSpaces = SkRect::MakeXYWH(0, 0, 0, 0);
1482                 // There are trailing spaces in this run
1483                 if (paragraphStyle.getTextAlign() == TextAlign::kJustify && isLastLine())
1484                 {
1485                     // TODO: this is just a patch. Make it right later (when it's clear what and how)
1486                     trailingSpaces = clip;
1487                     if(run->leftToRight()) {
1488                         trailingSpaces.fLeft = this->width();
1489                         clip.fRight = this->width();
1490                     } else {
1491                         trailingSpaces.fRight = 0;
1492                         clip.fLeft = 0;
1493                     }
1494                 } else if (paragraphStyle.getTextDirection() == TextDirection::kRtl &&
1495                     !run->leftToRight())
1496                 {
1497                     // Split
1498                     trailingSpaces = clip;
1499                     trailingSpaces.fLeft = - delta;
1500                     trailingSpaces.fRight = 0;
1501                     clip.fLeft += delta;
1502                 } else if (paragraphStyle.getTextDirection() == TextDirection::kLtr &&
1503                     run->leftToRight())
1504                 {
1505                     // Split
1506                     trailingSpaces = clip;
1507                     trailingSpaces.fLeft = this->width();
1508                     trailingSpaces.fRight = trailingSpaces.fLeft + delta;
1509                     clip.fRight -= delta;
1510                 }
1511             }
1512 
1513             clip.offset(this->offset());
1514             if (trailingSpaces.width() > 0) {
1515                 trailingSpaces.offset(this->offset());
1516             }
1517 
1518             // Check if we can merge two boxes instead of adding a new one
1519             auto merge = [&lastRun, &context, &boxes](SkRect clip) {
1520                 bool mergedBoxes = false;
1521                 if (!boxes.empty() &&
1522                     lastRun != nullptr &&
1523                     context.run->leftToRight() == lastRun->leftToRight() &&
1524                     lastRun->placeholderStyle() == nullptr &&
1525                     context.run->placeholderStyle() == nullptr &&
1526                     nearlyEqual(lastRun->heightMultiplier(),
1527                                 context.run->heightMultiplier()) &&
1528 #ifndef USE_SKIA_TXT
1529                     lastRun->font() == context.run->font())
1530 #else
1531                     IsRSFontEquals(lastRun->font(), context.run->font()))
1532 #endif
1533                 {
1534                     auto& lastBox = boxes.back();
1535                     if (nearlyEqual(lastBox.rect.fTop, clip.fTop) &&
1536                         nearlyEqual(lastBox.rect.fBottom, clip.fBottom) &&
1537                             (nearlyEqual(lastBox.rect.fLeft, clip.fRight) ||
1538                              nearlyEqual(lastBox.rect.fRight, clip.fLeft)))
1539                     {
1540                         lastBox.rect.fLeft = std::min(lastBox.rect.fLeft, clip.fLeft);
1541                         lastBox.rect.fRight = std::max(lastBox.rect.fRight, clip.fRight);
1542                         mergedBoxes = true;
1543                     }
1544                 }
1545                 lastRun = context.run;
1546                 return mergedBoxes;
1547             };
1548 
1549             if (!merge(clip)) {
1550                 boxes.emplace_back(clip, context.run->getTextDirection());
1551             }
1552             if (!nearlyZero(trailingSpaces.width()) && !merge(trailingSpaces)) {
1553                 boxes.emplace_back(trailingSpaces, paragraphStyle.getTextDirection());
1554             }
1555 
1556             if (rectWidthStyle == RectWidthStyle::kMax && !isLastLine()) {
1557                 // Align the very left/right box horizontally
1558                 auto lineStart = this->offset().fX;
1559                 auto lineEnd = this->offset().fX + this->width();
1560                 auto left = boxes[startBox];
1561                 auto right = boxes.back();
1562                 if (left.rect.fLeft > lineStart && left.direction == TextDirection::kRtl) {
1563                     left.rect.fRight = left.rect.fLeft;
1564                     left.rect.fLeft = 0;
1565                     boxes.insert(boxes.begin() + startBox + 1, left);
1566                 }
1567                 if (right.direction == TextDirection::kLtr &&
1568                     right.rect.fRight >= lineEnd &&
1569                     right.rect.fRight < fOwner->widthWithTrailingSpaces()) {
1570                     right.rect.fLeft = right.rect.fRight;
1571                     right.rect.fRight = fOwner->widthWithTrailingSpaces();
1572                     boxes.emplace_back(right);
1573                 }
1574             }
1575 
1576             return true;
1577         });
1578         return true;
1579     });
1580     if (fOwner->getApplyRoundingHack()) {
1581         for (auto& r : boxes) {
1582             r.rect.fLeft = littleRound(r.rect.fLeft);
1583             r.rect.fRight = littleRound(r.rect.fRight);
1584             r.rect.fTop = littleRound(r.rect.fTop);
1585             r.rect.fBottom = littleRound(r.rect.fBottom);
1586         }
1587     }
1588 }
1589 
1590 PositionWithAffinity TextLine::getGlyphPositionAtCoordinate(SkScalar dx) {
1591 
1592     if (SkScalarNearlyZero(this->width()) && SkScalarNearlyZero(this->spacesWidth())) {
1593         // TODO: this is one of the flutter changes that have to go away eventually
1594         //  Empty line is a special case in txtlib (but only when there are no spaces, too)
1595         auto utf16Index = fOwner->getUTF16Index(this->fTextExcludingSpaces.end);
1596         return { SkToS32(utf16Index) , kDownstream };
1597     }
1598 
1599     PositionWithAffinity result(0, Affinity::kDownstream);
1600     this->iterateThroughVisualRuns(true,
1601         [this, dx, &result]
1602         (const Run* run, SkScalar runOffsetInLine, TextRange textRange, SkScalar* runWidthInLine) {
1603             bool keepLooking = true;
1604             *runWidthInLine = this->iterateThroughSingleRunByStyles(
1605             TextAdjustment::GraphemeGluster, run, runOffsetInLine, textRange, StyleType::kNone,
1606             [this, run, dx, &result, &keepLooking]
1607             (TextRange textRange, const TextStyle& style, const TextLine::ClipContext& context0) {
1608 
1609                 SkScalar offsetX = this->offset().fX;
1610                 ClipContext context = context0;
1611 
1612                 // Correct the clip size because libtxt counts trailing spaces
1613                 if (run->leftToRight()) {
1614                     context.clip.fRight += context.fExcludedTrailingSpaces; // extending clip to the right
1615                 } else {
1616                     // Clip starts from 0; we cannot extend it to the left from that
1617                 }
1618                 // However, we need to offset the clip
1619                 context.clip.offset(offsetX, 0.0f);
1620 
1621                 // This patch will help us to avoid a floating point error
1622                 if (SkScalarNearlyEqual(context.clip.fRight, dx, 0.01f)) {
1623                     context.clip.fRight = dx;
1624                 }
1625 
1626                 if (dx <= context.clip.fLeft) {
1627                     // All the other runs are placed right of this one
1628                     auto utf16Index = fOwner->getUTF16Index(context.run->globalClusterIndex(context.pos));
1629                     if (run->leftToRight()) {
1630                         result = { SkToS32(utf16Index), kDownstream};
1631                         keepLooking = false;
1632                     } else {
1633                         result = { SkToS32(utf16Index + 1), kUpstream};
1634                         // If we haven't reached the end of the run we need to keep looking
1635                         keepLooking = context.pos != 0;
1636                     }
1637                     // For RTL we go another way
1638                     return !run->leftToRight();
1639                 }
1640 
1641                 if (dx >= context.clip.fRight) {
1642                     // We have to keep looking ; just in case keep the last one as the closest
1643                     auto utf16Index = fOwner->getUTF16Index(context.run->globalClusterIndex(context.pos + context.size));
1644                     if (run->leftToRight()) {
1645                         result = {SkToS32(utf16Index), kUpstream};
1646                     } else {
1647                         result = {SkToS32(utf16Index), kDownstream};
1648                     }
1649                     // For RTL we go another way
1650                     return run->leftToRight();
1651                 }
1652 
1653                 // So we found the run that contains our coordinates
1654                 // Find the glyph position in the run that is the closest left of our point
1655                 // TODO: binary search
1656                 size_t found = context.pos;
1657                 for (size_t index = context.pos; index < context.pos + context.size; ++index) {
1658                     // TODO: this rounding is done to match Flutter tests. Must be removed..
1659                     auto end = context.run->positionX(index) + context.fTextShift + offsetX;
1660                     if (fOwner->getApplyRoundingHack()) {
1661                         end = littleRound(end);
1662                     }
1663                     if (end > dx) {
1664                         break;
1665                     } else if (end == dx && !context.run->leftToRight()) {
1666                         // When we move RTL variable end points to the beginning of the code point which is included
1667                         found = index;
1668                         break;
1669                     }
1670                     found = index;
1671                 }
1672 
1673                 SkScalar glyphemePosLeft = context.run->positionX(found) + context.fTextShift + offsetX;
1674                 SkScalar glyphemesWidth = context.run->positionX(found + 1) - context.run->positionX(found);
1675 
1676                 // Find the grapheme range that contains the point
1677                 auto clusterIndex8 = context.run->globalClusterIndex(found);
1678                 auto clusterEnd8 = context.run->globalClusterIndex(found + 1);
1679                 auto graphemes = fOwner->countSurroundingGraphemes({clusterIndex8, clusterEnd8});
1680 
1681                 SkScalar center = glyphemePosLeft + glyphemesWidth * fOwner->getTextSplitRatio();
1682                 if (graphemes.size() > 1) {
1683                     // Calculate the position proportionally based on grapheme count
1684                     SkScalar averageGraphemeWidth = glyphemesWidth / graphemes.size();
1685                     SkScalar delta = dx - glyphemePosLeft;
1686                     int graphemeIndex = SkScalarNearlyZero(averageGraphemeWidth)
1687                                          ? 0
1688                                          : SkScalarFloorToInt(delta / averageGraphemeWidth);
1689                     auto graphemeCenter = glyphemePosLeft + graphemeIndex * averageGraphemeWidth +
1690                                           averageGraphemeWidth * fOwner->getTextSplitRatio();
1691                     auto graphemeUtf8Index = graphemes[graphemeIndex];
1692                     if ((dx < graphemeCenter) == context.run->leftToRight()) {
1693                         size_t utf16Index = fOwner->getUTF16Index(graphemeUtf8Index);
1694                         result = { SkToS32(utf16Index), kDownstream };
1695                     } else {
1696                         size_t utf16Index = fOwner->getUTF16Index(graphemeUtf8Index + 1);
1697                         result = { SkToS32(utf16Index), kUpstream };
1698                     }
1699                     // Keep UTF16 index as is
1700                 } else if ((dx < center) == context.run->leftToRight()) {
1701                     size_t utf16Index = fOwner->getUTF16Index(clusterIndex8);
1702                     result = { SkToS32(utf16Index), kDownstream };
1703                 } else {
1704                     size_t utf16Index = context.run->leftToRight()
1705                                                 ? fOwner->getUTF16Index(clusterEnd8)
1706                                                 : fOwner->getUTF16Index(clusterIndex8) + 1;
1707                     result = { SkToS32(utf16Index), kUpstream };
1708                 }
1709 
1710                 return keepLooking = false;
1711 
1712             });
1713             return keepLooking;
1714         }
1715     );
1716     return result;
1717 }
1718 
1719 void TextLine::getRectsForPlaceholders(std::vector<TextBox>& boxes) {
1720     this->iterateThroughVisualRuns(
1721         true,
1722         [&boxes, this](const Run* run, SkScalar runOffset, TextRange textRange,
1723                         SkScalar* width) {
1724                 auto context = this->measureTextInsideOneRun(
1725                         textRange, run, runOffset, 0, true, TextAdjustment::GraphemeGluster);
1726                 *width = context.clip.width();
1727 
1728             if (textRange.width() == 0) {
1729                 return true;
1730             }
1731             if (!run->isPlaceholder()) {
1732                 return true;
1733             }
1734 
1735             SkRect clip = context.clip;
1736             clip.offset(this->offset());
1737 
1738             if (fOwner->getApplyRoundingHack()) {
1739                 clip.fLeft = littleRound(clip.fLeft);
1740                 clip.fRight = littleRound(clip.fRight);
1741                 clip.fTop = littleRound(clip.fTop);
1742                 clip.fBottom = littleRound(clip.fBottom);
1743             }
1744             boxes.emplace_back(clip, run->getTextDirection());
1745             return true;
1746         });
1747 }
1748 
1749 size_t TextLine::getGlyphCount() const
1750 {
1751     size_t glyphCount = 0;
1752     for (auto& blob: fTextBlobCache) {
1753         glyphCount += blob.fVisitor_Size;
1754     }
1755     return glyphCount;
1756 }
1757 
1758 std::vector<std::unique_ptr<RunBase>> TextLine::getGlyphRuns() const
1759 {
1760     std::vector<std::unique_ptr<RunBase>> runBases;
1761     for (auto& blob: fTextBlobCache) {
1762         std::unique_ptr<RunBaseImpl> runBaseImplPtr = std::make_unique<RunBaseImpl>(
1763             blob.fBlob, blob.fOffset, blob.fPaint, blob.fClippingNeeded, blob.fClipRect,
1764             blob.fVisitor_Run, blob.fVisitor_Pos, blob.fVisitor_Size);
1765         runBases.emplace_back(std::move(runBaseImplPtr));
1766     }
1767     return runBases;
1768 }
1769 
1770 TextLine TextLine::CloneSelf()
1771 {
1772     TextLine textLine;
1773     textLine.fBlockRange = this->fBlockRange;
1774     textLine.fTextExcludingSpaces = this->fTextExcludingSpaces;
1775     textLine.fText = this->fText;
1776     textLine.fTextIncludingNewlines = this->fTextIncludingNewlines;
1777     textLine.fClusterRange = this->fClusterRange;
1778 
1779     textLine.fGhostClusterRange = this->fGhostClusterRange;
1780     textLine.fRunsInVisualOrder = this->fRunsInVisualOrder;
1781     textLine.fAdvance = this->fAdvance;
1782     textLine.fOffset = this->fOffset;
1783     textLine.fShift = this->fShift;
1784 
1785     textLine.fWidthWithSpaces = this->fWidthWithSpaces;
1786     if (this->fEllipsis) {
1787         textLine.fEllipsis = std::make_unique<Run>(*this->fEllipsis);
1788     }
1789 
1790     textLine.fSizes = this->fSizes;
1791     textLine.fMaxRunMetrics = this->fMaxRunMetrics;
1792     textLine.fHasBackground = this->fHasBackground;
1793     textLine.fHasShadows = this->fHasShadows;
1794     textLine.fHasDecorations = this->fHasDecorations;
1795     textLine.fAscentStyle = this->fAscentStyle;
1796     textLine.fDescentStyle = this->fDescentStyle;
1797     textLine.fTextBlobCachePopulated = this->fTextBlobCachePopulated;
1798 
1799     textLine.roundRectAttrs = this->roundRectAttrs;
1800     textLine.fTextBlobCache = this->fTextBlobCache;
1801     return textLine;
1802 }
1803 }  // namespace textlayout
1804 }  // namespace skia
1805