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