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