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