1 // Copyright 2019 Google LLC.
2 #include "include/core/SkBlurTypes.h"
3 #include "include/core/SkCanvas.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/ParagraphStyle.h"
17 #include "modules/skparagraph/include/TextShadow.h"
18 #include "modules/skparagraph/include/TextStyle.h"
19 #include "modules/skparagraph/src/Decorations.h"
20 #include "modules/skparagraph/src/ParagraphImpl.h"
21 #include "modules/skparagraph/src/TextLine.h"
22 #include "modules/skshaper/include/SkShaper.h"
23
24 #include <algorithm>
25 #include <iterator>
26 #include <limits>
27 #include <map>
28 #include <memory>
29 #include <tuple>
30 #include <type_traits>
31 #include <utility>
32
33 namespace skia {
34 namespace textlayout {
35
36 namespace {
37
38 // TODO: deal with all the intersection functionality
intersected(const TextRange & a,const TextRange & b)39 TextRange intersected(const TextRange& a, const TextRange& b) {
40 if (a.start == b.start && a.end == b.end) return a;
41 auto begin = std::max(a.start, b.start);
42 auto end = std::min(a.end, b.end);
43 return end >= begin ? TextRange(begin, end) : EMPTY_TEXT;
44 }
45
littleRound(SkScalar a)46 SkScalar littleRound(SkScalar a) {
47 // This rounding is done to match Flutter tests. Must be removed..
48 return SkScalarRoundToScalar(a * 100.0)/100.0;
49 }
50
operator *(const TextRange & a,const TextRange & b)51 TextRange operator*(const TextRange& a, const TextRange& b) {
52 if (a.start == b.start && a.end == b.end) return a;
53 auto begin = std::max(a.start, b.start);
54 auto end = std::min(a.end, b.end);
55 return end > begin ? TextRange(begin, end) : EMPTY_TEXT;
56 }
57
compareRound(SkScalar a,SkScalar b)58 int compareRound(SkScalar a, SkScalar b) {
59 // There is a rounding error that gets bigger when maxWidth gets bigger
60 // VERY long zalgo text (> 100000) on a VERY long line (> 10000)
61 // Canvas scaling affects it
62 // Letter spacing affects it
63 // It has to be relative to be useful
64 auto base = std::max(SkScalarAbs(a), SkScalarAbs(b));
65 auto diff = SkScalarAbs(a - b);
66 if (nearlyZero(base) || diff / base < 0.001f) {
67 return 0;
68 }
69
70 auto ra = littleRound(a);
71 auto rb = littleRound(b);
72 if (ra < rb) {
73 return -1;
74 } else {
75 return 1;
76 }
77 }
78
79 } // namespace
80
TextLine(ParagraphImpl * owner,SkVector offset,SkVector advance,BlockRange blocks,TextRange textExcludingSpaces,TextRange text,TextRange textIncludingNewlines,ClusterRange clusters,ClusterRange clustersWithGhosts,SkScalar widthWithSpaces,InternalLineMetrics sizes)81 TextLine::TextLine(ParagraphImpl* owner,
82 SkVector offset,
83 SkVector advance,
84 BlockRange blocks,
85 TextRange textExcludingSpaces,
86 TextRange text,
87 TextRange textIncludingNewlines,
88 ClusterRange clusters,
89 ClusterRange clustersWithGhosts,
90 SkScalar widthWithSpaces,
91 InternalLineMetrics sizes)
92 : fOwner(owner)
93 , fBlockRange(blocks)
94 , fTextExcludingSpaces(textExcludingSpaces)
95 , fText(text)
96 , fTextIncludingNewlines(textIncludingNewlines)
97 , fClusterRange(clusters)
98 , fGhostClusterRange(clustersWithGhosts)
99 , fRunsInVisualOrder()
100 , fAdvance(advance)
101 , fOffset(offset)
102 , fShift(0.0)
103 , fWidthWithSpaces(widthWithSpaces)
104 , fEllipsis(nullptr)
105 , fSizes(sizes)
106 , fHasBackground(false)
107 , fHasShadows(false)
108 , fHasDecorations(false)
109 , fAscentStyle(LineMetricStyle::CSS)
110 , fDescentStyle(LineMetricStyle::CSS)
111 , fTextBlobCachePopulated(false) {
112 // Reorder visual runs
113 auto& start = owner->cluster(fGhostClusterRange.start);
114 auto& end = owner->cluster(fGhostClusterRange.end - 1);
115 size_t numRuns = end.runIndex() - start.runIndex() + 1;
116
117 for (BlockIndex index = fBlockRange.start; index < fBlockRange.end; ++index) {
118 auto b = fOwner->styles().begin() + index;
119 if (b->fStyle.isPlaceholder()) {
120 continue;
121 }
122 if (b->fStyle.hasBackground()) {
123 fHasBackground = true;
124 }
125 if (b->fStyle.getDecorationType() != TextDecoration::kNoDecoration) {
126 fHasDecorations = true;
127 }
128 if (b->fStyle.getShadowNumber() > 0) {
129 fHasShadows = true;
130 }
131 }
132
133 // Get the logical order
134
135 // This is just chosen to catch the common/fast cases. Feel free to tweak.
136 constexpr int kPreallocCount = 4;
137 SkAutoSTArray<kPreallocCount, SkUnicode::BidiLevel> runLevels(numRuns);
138 size_t runLevelsIndex = 0;
139 for (auto runIndex = start.runIndex(); runIndex <= end.runIndex(); ++runIndex) {
140 auto& run = fOwner->run(runIndex);
141 runLevels[runLevelsIndex++] = run.fBidiLevel;
142 fMaxRunMetrics.add(
143 InternalLineMetrics(run.fFontMetrics.fAscent, run.fFontMetrics.fDescent, run.fFontMetrics.fLeading));
144 }
145 SkASSERT(runLevelsIndex == numRuns);
146
147 SkAutoSTArray<kPreallocCount, int32_t> logicalOrder(numRuns);
148
149 // TODO: hide all these logic in SkUnicode?
150 fOwner->getUnicode()->reorderVisual(runLevels.data(), numRuns, logicalOrder.data());
151 auto firstRunIndex = start.runIndex();
152 for (auto index : logicalOrder) {
153 fRunsInVisualOrder.push_back(firstRunIndex + index);
154 }
155
156 // TODO: This is the fix for flutter. Must be removed...
157 for (auto cluster = &start; cluster != &end; ++cluster) {
158 if (!cluster->run().isPlaceholder()) {
159 fShift += cluster->getHalfLetterSpacing();
160 break;
161 }
162 }
163 }
164
paint(SkCanvas * textCanvas,SkScalar x,SkScalar y)165 SkRect TextLine::paint(SkCanvas* textCanvas, SkScalar x, SkScalar y) {
166 auto bounds = SkRect::MakeEmpty();
167 if (this->empty()) {
168 return bounds;
169 }
170
171 if (fHasBackground) {
172 this->iterateThroughVisualRuns(false,
173 [textCanvas, x, y, this]
174 (const Run* run, SkScalar runOffsetInLine, TextRange textRange, SkScalar* runWidthInLine) {
175 *runWidthInLine = this->iterateThroughSingleRunByStyles(
176 run, runOffsetInLine, textRange, StyleType::kBackground,
177 [textCanvas, x, y, this](TextRange textRange, const TextStyle& style, const ClipContext& context) {
178 this->paintBackground(textCanvas, x, y, textRange, style, context);
179 });
180 return true;
181 });
182 }
183
184 if (fHasShadows) {
185 this->iterateThroughVisualRuns(false,
186 [textCanvas, x, y, &bounds, this]
187 (const Run* run, SkScalar runOffsetInLine, TextRange textRange, SkScalar* runWidthInLine) {
188 *runWidthInLine = this->iterateThroughSingleRunByStyles(
189 run, runOffsetInLine, textRange, StyleType::kShadow,
190 [textCanvas, x, y, &bounds, this](TextRange textRange, const TextStyle& style, const ClipContext& context) {
191 auto shadowBounds = this->paintShadow(textCanvas, x, y, textRange, style, context);
192 bounds.joinPossiblyEmptyRect(shadowBounds);
193 });
194 return true;
195 });
196 }
197
198 ensureTextBlobCachePopulated();
199
200 for (auto& record : fTextBlobCache) {
201 record.paint(textCanvas, x, y);
202 SkRect recordBounds = record.fBounds;
203 recordBounds.offset(x + this->offset().fX, y + this->offset().fY);
204 bounds.joinPossiblyEmptyRect(recordBounds);
205 }
206
207 if (fHasDecorations) {
208 this->iterateThroughVisualRuns(false,
209 [textCanvas, x, y, this]
210 (const Run* run, SkScalar runOffsetInLine, TextRange textRange, SkScalar* runWidthInLine) {
211 *runWidthInLine = this->iterateThroughSingleRunByStyles(
212 run, runOffsetInLine, textRange, StyleType::kDecorations,
213 [textCanvas, x, y, this](TextRange textRange, const TextStyle& style, const ClipContext& context) {
214 this->paintDecorations(textCanvas, x, y, textRange, style, context);
215 });
216 return true;
217 });
218 }
219
220 return bounds;
221 }
222
ensureTextBlobCachePopulated()223 void TextLine::ensureTextBlobCachePopulated() {
224 if (fTextBlobCachePopulated) {
225 return;
226 }
227 this->iterateThroughVisualRuns(false,
228 [this]
229 (const Run* run, SkScalar runOffsetInLine, TextRange textRange, SkScalar* runWidthInLine) {
230 if (run->placeholderStyle() != nullptr) {
231 *runWidthInLine = run->advance().fX;
232 return true;
233 }
234 *runWidthInLine = this->iterateThroughSingleRunByStyles(
235 run, runOffsetInLine, textRange, StyleType::kForeground,
236 [this](TextRange textRange, const TextStyle& style, const ClipContext& context) {
237 this->buildTextBlob(textRange, style, context);
238 });
239 return true;
240 });
241 fTextBlobCachePopulated = true;
242 }
243
format(TextAlign align,SkScalar maxWidth)244 void TextLine::format(TextAlign align, SkScalar maxWidth) {
245 SkScalar delta = maxWidth - this->width();
246 if (delta <= 0) {
247 return;
248 }
249
250 // We do nothing for left align
251 if (align == TextAlign::kJustify) {
252 if (!this->endsWithHardLineBreak()) {
253 this->justify(maxWidth);
254 } else if (fOwner->paragraphStyle().getTextDirection() == TextDirection::kRtl) {
255 // Justify -> Right align
256 fShift = delta;
257 }
258 } else if (align == TextAlign::kRight) {
259 fShift = delta;
260 } else if (align == TextAlign::kCenter) {
261 fShift = delta / 2;
262 }
263 }
264
scanStyles(StyleType styleType,const RunStyleVisitor & visitor)265 void TextLine::scanStyles(StyleType styleType, const RunStyleVisitor& visitor) {
266 if (this->empty()) {
267 return;
268 }
269
270 this->iterateThroughVisualRuns(false,
271 [this, visitor, styleType](const Run* run, SkScalar runOffset, TextRange textRange, SkScalar* width) {
272 *width = this->iterateThroughSingleRunByStyles(
273 run, runOffset, textRange, styleType,
274 [visitor](TextRange textRange, const TextStyle& style, const ClipContext& context) {
275 visitor(textRange, style, context);
276 });
277 return true;
278 });
279 }
280
extendHeight(const ClipContext & context) const281 SkRect TextLine::extendHeight(const ClipContext& context) const {
282 SkRect result = context.clip;
283 result.fBottom += std::max(this->fMaxRunMetrics.height() - this->height(), 0.0f);
284 return result;
285 }
286
metricsWithoutMultiplier(TextHeightBehavior correction)287 SkScalar TextLine::metricsWithoutMultiplier(TextHeightBehavior correction) {
288
289 if (this->fSizes.getForceStrut()) {
290 return 0;
291 }
292
293 InternalLineMetrics result;
294 this->iterateThroughVisualRuns(true,
295 [&result](const Run* run, SkScalar runOffset, TextRange textRange, SkScalar* width) {
296 InternalLineMetrics runMetrics(run->ascent(), run->descent(), run->leading());
297 result.add(runMetrics);
298 return true;
299 });
300 SkScalar delta = 0;
301 if (correction == TextHeightBehavior::kDisableFirstAscent) {
302 delta += (this->fSizes.fAscent - result.fAscent);
303 this->fSizes.fAscent = result.fAscent;
304 this->fAscentStyle = LineMetricStyle::Typographic;
305 } else if (correction == TextHeightBehavior::kDisableLastDescent) {
306 delta -= (this->fSizes.fDescent - result.fDescent);
307 this->fSizes.fDescent = result.fDescent;
308 this->fDescentStyle = LineMetricStyle::Typographic;
309 }
310 fAdvance.fY += delta;
311 return delta;
312 }
313
buildTextBlob(TextRange textRange,const TextStyle & style,const ClipContext & context)314 void TextLine::buildTextBlob(TextRange textRange, const TextStyle& style, const ClipContext& context) {
315 if (context.run->placeholderStyle() != nullptr) {
316 return;
317 }
318
319 fTextBlobCache.emplace_back();
320 TextBlobRecord& record = fTextBlobCache.back();
321
322 if (style.hasForeground()) {
323 record.fPaint = style.getForeground();
324 } else {
325 record.fPaint.setColor(style.getColor());
326 }
327 record.fVisitor_Run = context.run;
328 record.fVisitor_Pos = context.pos;
329
330 // TODO: This is the change for flutter, must be removed later
331 SkTextBlobBuilder builder;
332 context.run->copyTo(builder, SkToU32(context.pos), context.size);
333 record.fClippingNeeded = context.clippingNeeded;
334 if (context.clippingNeeded) {
335 record.fClipRect = extendHeight(context).makeOffset(this->offset());
336 } else {
337 record.fClipRect = context.clip.makeOffset(this->offset());
338 }
339
340 SkASSERT(nearlyEqual(context.run->baselineShift(), style.getBaselineShift()));
341 SkScalar correctedBaseline = SkScalarFloorToScalar(this->baseline() + style.getBaselineShift() + 0.5);
342 record.fBlob = builder.make();
343 if (record.fBlob != nullptr) {
344 record.fBounds.joinPossiblyEmptyRect(record.fBlob->bounds());
345 }
346
347 record.fOffset = SkPoint::Make(this->offset().fX + context.fTextShift,
348 this->offset().fY + correctedBaseline);
349 }
350
paint(SkCanvas * canvas,SkScalar x,SkScalar y)351 void TextLine::TextBlobRecord::paint(SkCanvas* canvas, SkScalar x, SkScalar y) {
352 if (fClippingNeeded) {
353 canvas->save();
354 canvas->clipRect(fClipRect.makeOffset(x, y));
355 }
356 canvas->drawTextBlob(fBlob, x + fOffset.x(), y + fOffset.y(), fPaint);
357 if (fClippingNeeded) {
358 canvas->restore();
359 }
360 }
361
paintBackground(SkCanvas * canvas,SkScalar x,SkScalar y,TextRange textRange,const TextStyle & style,const ClipContext & context) const362 void TextLine::paintBackground(SkCanvas* canvas, SkScalar x, SkScalar y, TextRange textRange, const TextStyle& style, const ClipContext& context) const {
363 if (style.hasBackground()) {
364 canvas->drawRect(context.clip.makeOffset(this->offset() + SkPoint::Make(x, y)), style.getBackground());
365 }
366 }
367
paintShadow(SkCanvas * canvas,SkScalar x,SkScalar y,TextRange textRange,const TextStyle & style,const ClipContext & context) const368 SkRect TextLine::paintShadow(SkCanvas* canvas, SkScalar x, SkScalar y, TextRange textRange, const TextStyle& style, const ClipContext& context) const {
369
370 SkScalar correctedBaseline = SkScalarFloorToScalar(this->baseline() + style.getBaselineShift() + 0.5);
371 SkRect shadowBounds = SkRect::MakeEmpty();
372
373 for (TextShadow shadow : style.getShadows()) {
374 if (!shadow.hasShadow()) continue;
375
376 SkPaint paint;
377 paint.setColor(shadow.fColor);
378 if (shadow.fBlurSigma != 0.0) {
379 auto filter = SkMaskFilter::MakeBlur(kNormal_SkBlurStyle,
380 SkDoubleToScalar(shadow.fBlurSigma), false);
381 paint.setMaskFilter(filter);
382 }
383
384 SkTextBlobBuilder builder;
385 context.run->copyTo(builder, context.pos, context.size);
386
387 if (context.clippingNeeded) {
388 canvas->save();
389 SkRect clip = extendHeight(context);
390 clip.offset(this->offset());
391 canvas->clipRect(clip);
392 }
393 auto blob = builder.make();
394 if (blob != nullptr) {
395 auto bounds = blob->bounds();
396 bounds.offset(
397 x + this->offset().fX + shadow.fOffset.x(),
398 y + this->offset().fY + shadow.fOffset.y()
399 );
400 shadowBounds.joinPossiblyEmptyRect(bounds);
401 }
402 canvas->drawTextBlob(blob,
403 x + this->offset().fX + shadow.fOffset.x() + context.fTextShift,
404 y + this->offset().fY + shadow.fOffset.y() + correctedBaseline,
405 paint);
406 if (context.clippingNeeded) {
407 canvas->restore();
408 }
409 }
410
411 return shadowBounds;
412 }
413
paintDecorations(SkCanvas * canvas,SkScalar x,SkScalar y,TextRange textRange,const TextStyle & style,const ClipContext & context) const414 void TextLine::paintDecorations(SkCanvas* canvas, SkScalar x, SkScalar y, TextRange textRange, const TextStyle& style, const ClipContext& context) const {
415
416 SkAutoCanvasRestore acr(canvas, true);
417 canvas->translate(x + this->offset().fX, y + this->offset().fY + style.getBaselineShift());
418 Decorations decorations;
419 SkScalar correctedBaseline = SkScalarFloorToScalar(this->baseline() + style.getBaselineShift() + 0.5);
420 decorations.paint(canvas, style, context, correctedBaseline);
421 }
422
justify(SkScalar maxWidth)423 void TextLine::justify(SkScalar maxWidth) {
424 // Count words and the extra spaces to spread across the line
425 // TODO: do it at the line breaking?..
426 size_t whitespacePatches = 0;
427 SkScalar textLen = 0;
428 bool whitespacePatch = false;
429 this->iterateThroughClustersInGlyphsOrder(false, false,
430 [&whitespacePatches, &textLen, &whitespacePatch](const Cluster* cluster, bool ghost) {
431 if (cluster->isWhitespaceBreak()) {
432 if (!whitespacePatch) {
433 whitespacePatch = true;
434 ++whitespacePatches;
435 }
436 } else {
437 whitespacePatch = false;
438 }
439 textLen += cluster->width();
440 return true;
441 });
442
443 if (whitespacePatches == 0) {
444 return;
445 }
446
447 SkScalar step = (maxWidth - textLen) / whitespacePatches;
448 SkScalar shift = 0;
449
450 // Deal with the ghost spaces
451 auto ghostShift = maxWidth - this->fAdvance.fX;
452 // Spread the extra whitespaces
453 whitespacePatch = false;
454 this->iterateThroughClustersInGlyphsOrder(false, true, [&](const Cluster* cluster, bool ghost) {
455
456 if (ghost) {
457 if (cluster->run().leftToRight()) {
458 shiftCluster(cluster, ghostShift, ghostShift);
459 }
460 return true;
461 }
462
463 auto prevShift = shift;
464 if (cluster->isWhitespaceBreak()) {
465 if (!whitespacePatch) {
466 shift += step;
467 whitespacePatch = true;
468 --whitespacePatches;
469 }
470 } else {
471 whitespacePatch = false;
472 }
473 shiftCluster(cluster, shift, prevShift);
474 return true;
475 });
476
477 SkAssertResult(nearlyEqual(shift, maxWidth - textLen));
478 SkASSERT(whitespacePatches == 0);
479
480 this->fWidthWithSpaces += ghostShift;
481 this->fAdvance.fX = maxWidth;
482 }
483
shiftCluster(const Cluster * cluster,SkScalar shift,SkScalar prevShift)484 void TextLine::shiftCluster(const Cluster* cluster, SkScalar shift, SkScalar prevShift) {
485
486 auto& run = cluster->run();
487 auto start = cluster->startPos();
488 auto end = cluster->endPos();
489
490 if (end == run.size()) {
491 // Set the same shift for the fake last glyph (to avoid all extra checks)
492 ++end;
493 }
494
495 if (run.fJustificationShifts.empty()) {
496 // Do not fill this array until needed
497 run.fJustificationShifts.push_back_n(run.size() + 1, { 0, 0 });
498 }
499
500 for (size_t pos = start; pos < end; ++pos) {
501 run.fJustificationShifts[pos] = { shift, prevShift };
502 }
503 }
504
createEllipsis(SkScalar maxWidth,const SkString & ellipsis,bool)505 void TextLine::createEllipsis(SkScalar maxWidth, const SkString& ellipsis, bool) {
506 // Replace some clusters with the ellipsis
507 // Go through the clusters in the reverse logical order
508 // taking off cluster by cluster until the ellipsis fits
509 SkScalar width = fAdvance.fX;
510
511 auto attachEllipsis = [&](const Cluster* cluster){
512 // Shape the ellipsis
513 std::unique_ptr<Run> run = shapeEllipsis(ellipsis, cluster->run());
514 run->fClusterStart = cluster->textRange().start;
515 run->setOwner(fOwner);
516
517 // See if it fits
518 if (width + run->advance().fX > maxWidth) {
519 width -= cluster->width();
520 // Continue if it's not
521 return false;
522 }
523
524 fEllipsis = std::move(run);
525 fEllipsis->shift(width, 0);
526 fAdvance.fX = width;
527 return true;
528 };
529
530 iterateThroughClustersInGlyphsOrder(
531 true, false, [&](const Cluster* cluster, bool ghost) {
532 return !attachEllipsis(cluster);
533 });
534
535 if (!fEllipsis) {
536 // Weird situation: just the ellipsis on the line (if it fits)
537 attachEllipsis(&fOwner->cluster(clusters().start));
538 }
539 }
540
shapeEllipsis(const SkString & ellipsis,const Run & run)541 std::unique_ptr<Run> TextLine::shapeEllipsis(const SkString& ellipsis, const Run& run) {
542
543 class ShapeHandler final : public SkShaper::RunHandler {
544 public:
545 ShapeHandler(SkScalar lineHeight, bool useHalfLeading, SkScalar baselineShift, const SkString& ellipsis)
546 : fRun(nullptr), fLineHeight(lineHeight), fUseHalfLeading(useHalfLeading), fBaselineShift(baselineShift), fEllipsis(ellipsis) {}
547 Run* run() & { return fRun.get(); }
548 std::unique_ptr<Run> run() && { return std::move(fRun); }
549
550 private:
551 void beginLine() override {}
552
553 void runInfo(const RunInfo&) override {}
554
555 void commitRunInfo() override {}
556
557 Buffer runBuffer(const RunInfo& info) override {
558 SkASSERT(!fRun);
559 fRun = std::make_unique<Run>(nullptr, info, 0, fLineHeight, fUseHalfLeading, fBaselineShift, 0, 0);
560 return fRun->newRunBuffer();
561 }
562
563 void commitRunBuffer(const RunInfo& info) override {
564 fRun->fAdvance.fX = info.fAdvance.fX;
565 fRun->fAdvance.fY = fRun->advance().fY;
566 fRun->fPlaceholderIndex = std::numeric_limits<size_t>::max();
567 fRun->fEllipsis = true;
568 }
569
570 void commitLine() override {}
571
572 std::unique_ptr<Run> fRun;
573 SkScalar fLineHeight;
574 bool fUseHalfLeading;
575 SkScalar fBaselineShift;
576 SkString fEllipsis;
577 };
578
579 ShapeHandler handler(run.heightMultiplier(), run.useHalfLeading(), run.baselineShift(), ellipsis);
580 std::unique_ptr<SkShaper> shaper = SkShaper::MakeShapeDontWrapOrReorder();
581 SkASSERT_RELEASE(shaper != nullptr);
582 shaper->shape(ellipsis.c_str(), ellipsis.size(), run.font(), true,
583 std::numeric_limits<SkScalar>::max(), &handler);
584 handler.run()->fTextRange = TextRange(0, ellipsis.size());
585 handler.run()->fOwner = fOwner;
586 return std::move(handler).run();
587 }
588
measureTextInsideOneRun(TextRange textRange,const Run * run,SkScalar runOffsetInLine,SkScalar textOffsetInRunInLine,bool includeGhostSpaces,bool limitToGraphemes) const589 TextLine::ClipContext TextLine::measureTextInsideOneRun(TextRange textRange,
590 const Run* run,
591 SkScalar runOffsetInLine,
592 SkScalar textOffsetInRunInLine,
593 bool includeGhostSpaces,
594 bool limitToGraphemes) const {
595 ClipContext result = { run, 0, run->size(), 0, SkRect::MakeEmpty(), 0, false };
596
597 if (run->fEllipsis) {
598 // Both ellipsis and placeholders can only be measured as one glyph
599 SkASSERT(textRange == run->textRange());
600 result.fTextShift = runOffsetInLine;
601 result.clip = SkRect::MakeXYWH(runOffsetInLine,
602 sizes().runTop(run, this->fAscentStyle),
603 run->advance().fX,
604 run->calculateHeight(this->fAscentStyle,this->fDescentStyle));
605 return result;
606 } else if (run->isPlaceholder()) {
607 result.fTextShift = runOffsetInLine;
608 if (SkScalarIsFinite(run->fFontMetrics.fAscent)) {
609 result.clip = SkRect::MakeXYWH(runOffsetInLine,
610 sizes().runTop(run, this->fAscentStyle),
611 run->advance().fX,
612 run->calculateHeight(this->fAscentStyle,this->fDescentStyle));
613 } else {
614 result.clip = SkRect::MakeXYWH(runOffsetInLine, run->fFontMetrics.fAscent, run->advance().fX, 0);
615 }
616 return result;
617 }
618 // Find [start:end] clusters for the text
619 Cluster* start = nullptr;
620 Cluster* end = nullptr;
621 do {
622 bool found;
623 ClusterIndex startIndex;
624 ClusterIndex endIndex;
625 std::tie(found, startIndex, endIndex) = run->findLimitingClusters(textRange);
626 if (!found) {
627 return result;
628 }
629 start = &fOwner->cluster(startIndex);
630 end = &fOwner->cluster(endIndex);
631
632 if (!limitToGraphemes) {
633 break;
634 }
635
636 // Update textRange by cluster edges
637 if (run->leftToRight()) {
638 if (textRange.start != start->textRange().start) {
639 textRange.start = start->textRange().end;
640 }
641 textRange.end = end->textRange().end;
642 } else {
643 if (textRange.start != end->textRange().start) {
644 textRange.start = end->textRange().end;
645 }
646 textRange.end = start->textRange().end;
647 }
648
649 std::tie(found, startIndex, endIndex) = run->findLimitingGraphemes(textRange);
650 if (startIndex == textRange.start && endIndex == textRange.end) {
651 break;
652 }
653
654 // Some clusters are inside graphemes and we need to adjust them
655 //SkDebugf("Correct range: [%d:%d) -> [%d:%d)\n", textRange.start, textRange.end, startIndex, endIndex);
656 textRange.start = startIndex;
657 textRange.end = endIndex;
658
659 // Move the start until it's on the grapheme edge (and glypheme, too)
660 } while (true);
661
662 result.pos = start->startPos();
663 result.size = (end->isHardBreak() ? end->startPos() : end->endPos()) - start->startPos();
664
665 auto textStartInRun = run->positionX(start->startPos());
666 auto textStartInLine = runOffsetInLine + textOffsetInRunInLine;
667 /*
668 if (!run->fJustificationShifts.empty()) {
669 SkDebugf("Justification for [%d:%d)\n", textRange.start, textRange.end);
670 for (auto i = result.pos; i < result.pos + result.size; ++i) {
671 auto j = run->fJustificationShifts[i];
672 SkDebugf("[%d] = %f %f\n", i, j.fX, j.fY);
673 }
674 }
675 */
676 // Calculate the clipping rectangle for the text with cluster edges
677 // There are 2 cases:
678 // EOL (when we expect the last cluster clipped without any spaces)
679 // Anything else (when we want the cluster width contain all the spaces -
680 // coming from letter spacing or word spacing or justification)
681 result.clip =
682 SkRect::MakeXYWH(0,
683 sizes().runTop(run, this->fAscentStyle),
684 run->calculateWidth(result.pos, result.pos + result.size, false),
685 run->calculateHeight(this->fAscentStyle,this->fDescentStyle));
686
687 // Correct the width in case the text edges don't match clusters
688 // TODO: This is where we get smart about selecting a part of a cluster
689 // by shaping each grapheme separately and then use the result sizes
690 // to calculate the proportions
691 auto leftCorrection = start->sizeToChar(textRange.start);
692 auto rightCorrection = end->sizeFromChar(textRange.end - 1);
693 result.clip.fLeft += leftCorrection;
694 result.clip.fRight -= rightCorrection;
695 result.clippingNeeded = leftCorrection != 0 || rightCorrection != 0;
696
697 textStartInLine -= leftCorrection;
698 result.clip.offset(textStartInLine, 0);
699
700 if (compareRound(result.clip.fRight, fAdvance.fX) > 0 && !includeGhostSpaces) {
701 // There are few cases when we need it.
702 // The most important one: we measure the text with spaces at the end (or at the beginning in RTL)
703 // and we should ignore these spaces
704 if (run->leftToRight()) {
705 // We only use this member for LTR
706 result.fExcludedTrailingSpaces = std::max(result.clip.fRight - fAdvance.fX, 0.0f);
707 }
708 result.clippingNeeded = true;
709 result.clip.fRight = fAdvance.fX;
710 }
711
712 if (result.clip.width() < 0) {
713 // Weird situation when glyph offsets move the glyph to the left
714 // (happens with zalgo texts, for instance)
715 result.clip.fRight = result.clip.fLeft;
716 }
717
718 // The text must be aligned with the lineOffset
719 result.fTextShift = textStartInLine - textStartInRun;
720
721 return result;
722 }
723
iterateThroughClustersInGlyphsOrder(bool reversed,bool includeGhosts,const ClustersVisitor & visitor) const724 void TextLine::iterateThroughClustersInGlyphsOrder(bool reversed,
725 bool includeGhosts,
726 const ClustersVisitor& visitor) const {
727 // Walk through the clusters in the logical order (or reverse)
728 SkSpan<const size_t> runs(fRunsInVisualOrder.data(), fRunsInVisualOrder.size());
729 bool ignore = false;
730 directional_for_each(runs, !reversed, [&](decltype(runs[0]) r) {
731 if (ignore) return;
732 auto run = this->fOwner->run(r);
733 auto trimmedRange = fClusterRange.intersection(run.clusterRange());
734 auto trailedRange = fGhostClusterRange.intersection(run.clusterRange());
735 SkASSERT(trimmedRange.start == trailedRange.start);
736
737 auto trailed = fOwner->clusters(trailedRange);
738 auto trimmed = fOwner->clusters(trimmedRange);
739 directional_for_each(trailed, reversed != run.leftToRight(), [&](Cluster& cluster) {
740 if (ignore) return;
741 bool ghost = &cluster >= trimmed.end();
742 if (!includeGhosts && ghost) {
743 return;
744 }
745 if (!visitor(&cluster, ghost)) {
746 ignore = true;
747 return;
748 }
749 });
750 });
751 }
752
iterateThroughSingleRunByStyles(const Run * run,SkScalar runOffset,TextRange textRange,StyleType styleType,const RunStyleVisitor & visitor) const753 SkScalar TextLine::iterateThroughSingleRunByStyles(const Run* run,
754 SkScalar runOffset,
755 TextRange textRange,
756 StyleType styleType,
757 const RunStyleVisitor& visitor) const {
758
759 if (run->fEllipsis) {
760 // Extra efforts to get the ellipsis text style
761 ClipContext clipContext = this->measureTextInsideOneRun(run->textRange(), run, runOffset,
762 0, false, true);
763 TextRange testRange(run->fClusterStart, run->fClusterStart + 1);
764 for (BlockIndex index = fBlockRange.start; index < fBlockRange.end; ++index) {
765 auto block = fOwner->styles().begin() + index;
766 auto intersect = intersected(block->fRange, testRange);
767 if (intersect.width() > 0) {
768 visitor(textRange, block->fStyle, clipContext);
769 return run->advance().fX;
770 }
771 }
772 SkASSERT(false);
773 }
774
775 if (styleType == StyleType::kNone) {
776 ClipContext clipContext = this->measureTextInsideOneRun(textRange, run, runOffset,
777 0, false, true);
778 if (clipContext.clip.height() > 0) {
779 visitor(textRange, TextStyle(), clipContext);
780 return clipContext.clip.width();
781 } else {
782 return 0;
783 }
784 }
785
786 TextIndex start = EMPTY_INDEX;
787 size_t size = 0;
788 const TextStyle* prevStyle = nullptr;
789 SkScalar textOffsetInRun = 0;
790
791 const BlockIndex blockRangeSize = fBlockRange.end - fBlockRange.start;
792 for (BlockIndex index = 0; index <= blockRangeSize; ++index) {
793
794 TextRange intersect;
795 TextStyle* style = nullptr;
796 if (index < blockRangeSize) {
797 auto block = fOwner->styles().begin() +
798 (run->leftToRight() ? fBlockRange.start + index : fBlockRange.end - index - 1);
799
800 // Get the text
801 intersect = intersected(block->fRange, textRange);
802 if (intersect.width() == 0) {
803 if (start == EMPTY_INDEX) {
804 // This style is not applicable to the text yet
805 continue;
806 } else {
807 // We have found all the good styles already
808 // but we need to process the last one of them
809 intersect = TextRange(start, start + size);
810 index = fBlockRange.end;
811 }
812 } else {
813 // Get the style
814 style = &block->fStyle;
815 if (start != EMPTY_INDEX && style->matchOneAttribute(styleType, *prevStyle)) {
816 size += intersect.width();
817 // RTL text intervals move backward
818 start = std::min(intersect.start, start);
819 continue;
820 } else if (start == EMPTY_INDEX ) {
821 // First time only
822 prevStyle = style;
823 size = intersect.width();
824 start = intersect.start;
825 continue;
826 }
827 }
828 } else if (prevStyle != nullptr) {
829 // This is the last style
830 } else {
831 break;
832 }
833
834 // We have the style and the text
835 auto runStyleTextRange = TextRange(start, start + size);
836 // Measure the text
837 ClipContext clipContext = this->measureTextInsideOneRun(runStyleTextRange, run, runOffset,
838 textOffsetInRun, false, true);
839 if (clipContext.clip.height() == 0) {
840 continue;
841 }
842 visitor(runStyleTextRange, *prevStyle, clipContext);
843 textOffsetInRun += clipContext.clip.width();
844
845 // Start all over again
846 prevStyle = style;
847 start = intersect.start;
848 size = intersect.width();
849 }
850 return textOffsetInRun;
851 }
852
iterateThroughVisualRuns(bool includingGhostSpaces,const RunVisitor & visitor) const853 void TextLine::iterateThroughVisualRuns(bool includingGhostSpaces, const RunVisitor& visitor) const {
854
855 // Walk through all the runs that intersect with the line in visual order
856 SkScalar width = 0;
857 SkScalar runOffset = 0;
858 SkScalar totalWidth = 0;
859 auto textRange = includingGhostSpaces ? this->textWithNewlines() : this->trimmedText();
860 for (auto& runIndex : fRunsInVisualOrder) {
861
862 const auto run = &this->fOwner->run(runIndex);
863 auto lineIntersection = intersected(run->textRange(), textRange);
864 if (lineIntersection.width() == 0 && this->width() != 0) {
865 // TODO: deal with empty runs in a better way
866 continue;
867 }
868 if (!run->leftToRight() && runOffset == 0 && includingGhostSpaces) {
869 // runOffset does not take in account a possibility
870 // that RTL run could start before the line (trailing spaces)
871 // so we need to do runOffset -= "trailing whitespaces length"
872 TextRange whitespaces = intersected(
873 TextRange(fTextExcludingSpaces.end, fTextIncludingNewlines.end), run->fTextRange);
874 if (whitespaces.width() > 0) {
875 auto whitespacesLen = measureTextInsideOneRun(whitespaces, run, runOffset, 0, true, false).clip.width();
876 runOffset -= whitespacesLen;
877 }
878 }
879 runOffset += width;
880 totalWidth += width;
881 if (!visitor(run, runOffset, lineIntersection, &width)) {
882 return;
883 }
884 }
885
886 runOffset += width;
887 totalWidth += width;
888
889 if (this->ellipsis() != nullptr) {
890 if (visitor(ellipsis(), runOffset, ellipsis()->textRange(), &width)) {
891 totalWidth += width;
892 }
893 }
894
895 // This is a very important assert!
896 // It asserts that 2 different ways of calculation come with the same results
897 if (!includingGhostSpaces && compareRound(totalWidth, this->width()) != 0) {
898 SkDebugf("ASSERT: %f != %f\n", totalWidth, this->width());
899 SkASSERT(false);
900 }
901 }
902
offset() const903 SkVector TextLine::offset() const {
904 return fOffset + SkVector::Make(fShift, 0);
905 }
906
getMetrics() const907 LineMetrics TextLine::getMetrics() const {
908 LineMetrics result;
909
910 // Fill out the metrics
911 fOwner->ensureUTF16Mapping();
912 result.fStartIndex = fOwner->getUTF16Index(fTextExcludingSpaces.start);
913 result.fEndExcludingWhitespaces = fOwner->getUTF16Index(fTextExcludingSpaces.end);
914 result.fEndIndex = fOwner->getUTF16Index(fText.end);
915 result.fEndIncludingNewline = fOwner->getUTF16Index(fTextIncludingNewlines.end);
916 result.fHardBreak = endsWithHardLineBreak();
917 result.fAscent = - fMaxRunMetrics.ascent();
918 result.fDescent = fMaxRunMetrics.descent();
919 result.fUnscaledAscent = - fMaxRunMetrics.ascent(); // TODO: implement
920 result.fHeight = littleRound(fAdvance.fY);
921 result.fWidth = littleRound(fAdvance.fX);
922 result.fLeft = this->offset().fX;
923 // This is Flutter definition of a baseline
924 result.fBaseline = this->offset().fY + this->height() - this->sizes().descent();
925 result.fLineNumber = this - fOwner->lines().begin();
926
927 // Fill out the style parts
928 this->iterateThroughVisualRuns(false,
929 [this, &result]
930 (const Run* run, SkScalar runOffsetInLine, TextRange textRange, SkScalar* runWidthInLine) {
931 if (run->placeholderStyle() != nullptr) {
932 *runWidthInLine = run->advance().fX;
933 return true;
934 }
935 *runWidthInLine = this->iterateThroughSingleRunByStyles(
936 run, runOffsetInLine, textRange, StyleType::kForeground,
937 [&result, &run](TextRange textRange, const TextStyle& style, const ClipContext& context) {
938 SkFontMetrics fontMetrics;
939 run->fFont.getMetrics(&fontMetrics);
940 StyleMetrics styleMetrics(&style, fontMetrics);
941 result.fLineMetrics.emplace(textRange.start, styleMetrics);
942 });
943 return true;
944 });
945
946 return result;
947 }
948
isFirstLine()949 bool TextLine::isFirstLine() {
950 return this == &fOwner->lines().front();
951 }
952
isLastLine()953 bool TextLine::isLastLine() {
954 return this == &fOwner->lines().back();
955 }
956
endsWithHardLineBreak() const957 bool TextLine::endsWithHardLineBreak() const {
958 // TODO: For some reason Flutter imagines a hard line break at the end of the last line.
959 // To be removed...
960 return fOwner->cluster(fGhostClusterRange.end - 1).isHardBreak() ||
961 fEllipsis != nullptr ||
962 fGhostClusterRange.end == fOwner->clusters().size() - 1;
963 }
964
getRectsForRange(TextRange textRange0,RectHeightStyle rectHeightStyle,RectWidthStyle rectWidthStyle,std::vector<TextBox> & boxes)965 void TextLine::getRectsForRange(TextRange textRange0,
966 RectHeightStyle rectHeightStyle,
967 RectWidthStyle rectWidthStyle,
968 std::vector<TextBox>& boxes)
969 {
970 const Run* lastRun = nullptr;
971 auto startBox = boxes.size();
972 this->iterateThroughVisualRuns(true,
973 [textRange0, rectHeightStyle, rectWidthStyle, &boxes, &lastRun, startBox, this]
974 (const Run* run, SkScalar runOffsetInLine, TextRange textRange, SkScalar* runWidthInLine) {
975 *runWidthInLine = this->iterateThroughSingleRunByStyles(
976 run, runOffsetInLine, textRange, StyleType::kNone,
977 [run, runOffsetInLine, textRange0, rectHeightStyle, rectWidthStyle, &boxes, &lastRun, startBox, this]
978 (TextRange textRange, const TextStyle& style, const TextLine::ClipContext& lineContext) {
979
980 auto intersect = textRange * textRange0;
981 if (intersect.empty()) {
982 return true;
983 }
984
985 auto paragraphStyle = fOwner->paragraphStyle();
986
987 // Found a run that intersects with the text
988 auto context = this->measureTextInsideOneRun(intersect, run, runOffsetInLine, 0, true, true);
989 SkRect clip = context.clip;
990 clip.offset(lineContext.fTextShift - context.fTextShift, 0);
991
992 switch (rectHeightStyle) {
993 case RectHeightStyle::kMax:
994 // TODO: Change it once flutter rolls into google3
995 // (probably will break things if changed before)
996 clip.fBottom = this->height();
997 clip.fTop = this->sizes().delta();
998 break;
999 case RectHeightStyle::kIncludeLineSpacingTop: {
1000 if (isFirstLine()) {
1001 auto verticalShift = this->sizes().runTop(context.run, LineMetricStyle::Typographic);
1002 clip.fTop += verticalShift;
1003 }
1004 break;
1005 }
1006 case RectHeightStyle::kIncludeLineSpacingMiddle: {
1007 auto verticalShift = this->sizes().runTop(context.run, LineMetricStyle::Typographic);
1008 clip.fTop += isFirstLine() ? verticalShift : verticalShift / 2;
1009 clip.fBottom += isLastLine() ? 0 : verticalShift / 2;
1010 break;
1011 }
1012 case RectHeightStyle::kIncludeLineSpacingBottom: {
1013 auto verticalShift = this->sizes().runTop(context.run, LineMetricStyle::Typographic);
1014 clip.offset(0, verticalShift);
1015 if (isLastLine()) {
1016 clip.fBottom -= verticalShift;
1017 }
1018 break;
1019 }
1020 case RectHeightStyle::kStrut: {
1021 const auto& strutStyle = paragraphStyle.getStrutStyle();
1022 if (strutStyle.getStrutEnabled()
1023 && strutStyle.getFontSize() > 0) {
1024 auto strutMetrics = fOwner->strutMetrics();
1025 auto top = this->baseline();
1026 clip.fTop = top + strutMetrics.ascent();
1027 clip.fBottom = top + strutMetrics.descent();
1028 }
1029 }
1030 break;
1031 case RectHeightStyle::kTight: {
1032 if (run->fHeightMultiplier <= 0) {
1033 break;
1034 }
1035 const auto effectiveBaseline = this->baseline() + this->sizes().delta();
1036 clip.fTop = effectiveBaseline + run->ascent();
1037 clip.fBottom = effectiveBaseline + run->descent();
1038 }
1039 break;
1040 default:
1041 SkASSERT(false);
1042 break;
1043 }
1044
1045 // Separate trailing spaces and move them in the default order of the paragraph
1046 // in case the run order and the paragraph order don't match
1047 SkRect trailingSpaces = SkRect::MakeEmpty();
1048 if (this->trimmedText().end <this->textWithNewlines().end && // Line has trailing space
1049 this->textWithNewlines().end == intersect.end && // Range is at the end of the line
1050 this->trimmedText().end > intersect.start) // Range has more than just spaces
1051 {
1052 auto delta = this->spacesWidth();
1053 trailingSpaces = SkRect::MakeXYWH(0, 0, 0, 0);
1054 // There are trailing spaces in this run
1055 if (paragraphStyle.getTextAlign() == TextAlign::kJustify && isLastLine())
1056 {
1057 // TODO: this is just a patch. Make it right later (when it's clear what and how)
1058 trailingSpaces = clip;
1059 if(run->leftToRight()) {
1060 trailingSpaces.fLeft = this->width();
1061 clip.fRight = this->width();
1062 } else {
1063 trailingSpaces.fRight = 0;
1064 clip.fLeft = 0;
1065 }
1066 } else if (paragraphStyle.getTextDirection() == TextDirection::kRtl &&
1067 !run->leftToRight())
1068 {
1069 // Split
1070 trailingSpaces = clip;
1071 trailingSpaces.fLeft = - delta;
1072 trailingSpaces.fRight = 0;
1073 clip.fLeft += delta;
1074 } else if (paragraphStyle.getTextDirection() == TextDirection::kLtr &&
1075 run->leftToRight())
1076 {
1077 // Split
1078 trailingSpaces = clip;
1079 trailingSpaces.fLeft = this->width();
1080 trailingSpaces.fRight = trailingSpaces.fLeft + delta;
1081 clip.fRight -= delta;
1082 }
1083 }
1084
1085 clip.offset(this->offset());
1086 if (trailingSpaces.width() > 0) {
1087 trailingSpaces.offset(this->offset());
1088 }
1089
1090 // Check if we can merge two boxes instead of adding a new one
1091 auto merge = [&lastRun, &context, &boxes](SkRect clip) {
1092 bool mergedBoxes = false;
1093 if (!boxes.empty() &&
1094 lastRun != nullptr &&
1095 lastRun->placeholderStyle() == nullptr &&
1096 context.run->placeholderStyle() == nullptr &&
1097 nearlyEqual(lastRun->heightMultiplier(),
1098 context.run->heightMultiplier()) &&
1099 lastRun->font() == context.run->font())
1100 {
1101 auto& lastBox = boxes.back();
1102 if (nearlyEqual(lastBox.rect.fTop, clip.fTop) &&
1103 nearlyEqual(lastBox.rect.fBottom, clip.fBottom) &&
1104 (nearlyEqual(lastBox.rect.fLeft, clip.fRight) ||
1105 nearlyEqual(lastBox.rect.fRight, clip.fLeft)))
1106 {
1107 lastBox.rect.fLeft = std::min(lastBox.rect.fLeft, clip.fLeft);
1108 lastBox.rect.fRight = std::max(lastBox.rect.fRight, clip.fRight);
1109 mergedBoxes = true;
1110 }
1111 }
1112 lastRun = context.run;
1113 return mergedBoxes;
1114 };
1115
1116 if (!merge(clip)) {
1117 boxes.emplace_back(clip, context.run->getTextDirection());
1118 }
1119 if (!nearlyZero(trailingSpaces.width()) && !merge(trailingSpaces)) {
1120 boxes.emplace_back(trailingSpaces, paragraphStyle.getTextDirection());
1121 }
1122
1123 if (rectWidthStyle == RectWidthStyle::kMax && !isLastLine()) {
1124 // Align the very left/right box horizontally
1125 auto lineStart = this->offset().fX;
1126 auto lineEnd = this->offset().fX + this->width();
1127 auto left = boxes[startBox];
1128 auto right = boxes.back();
1129 if (left.rect.fLeft > lineStart && left.direction == TextDirection::kRtl) {
1130 left.rect.fRight = left.rect.fLeft;
1131 left.rect.fLeft = 0;
1132 boxes.insert(boxes.begin() + startBox + 1, left);
1133 }
1134 if (right.direction == TextDirection::kLtr &&
1135 right.rect.fRight >= lineEnd &&
1136 right.rect.fRight < fOwner->widthWithTrailingSpaces()) {
1137 right.rect.fLeft = right.rect.fRight;
1138 right.rect.fRight = fOwner->widthWithTrailingSpaces();
1139 boxes.emplace_back(right);
1140 }
1141 }
1142
1143 return true;
1144 });
1145 return true;
1146 });
1147 for (auto& r : boxes) {
1148 r.rect.fLeft = littleRound(r.rect.fLeft);
1149 r.rect.fRight = littleRound(r.rect.fRight);
1150 r.rect.fTop = littleRound(r.rect.fTop);
1151 r.rect.fBottom = littleRound(r.rect.fBottom);
1152 }
1153 }
1154
getGlyphPositionAtCoordinate(SkScalar dx)1155 PositionWithAffinity TextLine::getGlyphPositionAtCoordinate(SkScalar dx) {
1156
1157 if (SkScalarNearlyZero(this->width()) && SkScalarNearlyZero(this->spacesWidth())) {
1158 // TODO: this is one of the flutter changes that have to go away eventually
1159 // Empty line is a special case in txtlib (but only when there are no spaces, too)
1160 auto utf16Index = fOwner->getUTF16Index(this->fTextExcludingSpaces.end);
1161 return { SkToS32(utf16Index) , kDownstream };
1162 }
1163
1164 PositionWithAffinity result(0, Affinity::kDownstream);
1165 this->iterateThroughVisualRuns(true,
1166 [this, dx, &result]
1167 (const Run* run, SkScalar runOffsetInLine, TextRange textRange, SkScalar* runWidthInLine) {
1168 bool keepLooking = true;
1169 *runWidthInLine = this->iterateThroughSingleRunByStyles(
1170 run, runOffsetInLine, textRange, StyleType::kNone,
1171 [this, run, dx, &result, &keepLooking]
1172 (TextRange textRange, const TextStyle& style, const TextLine::ClipContext& context0) {
1173
1174 SkScalar offsetX = this->offset().fX;
1175 ClipContext context = context0;
1176
1177 // Correct the clip size because libtxt counts trailing spaces
1178 if (run->leftToRight()) {
1179 context.clip.fRight += context.fExcludedTrailingSpaces; // extending clip to the right
1180 } else {
1181 // Clip starts from 0; we cannot extend it to the left from that
1182 }
1183 // This patch will help us to avoid a floating point error
1184 if (SkScalarNearlyEqual(context.clip.fRight, dx - offsetX, 0.01f)) {
1185 context.clip.fRight = dx - offsetX;
1186 }
1187
1188 if (dx < context.clip.fLeft + offsetX) {
1189 // All the other runs are placed right of this one
1190 auto utf16Index = fOwner->getUTF16Index(context.run->globalClusterIndex(context.pos));
1191 if (run->leftToRight()) {
1192 result = { SkToS32(utf16Index), kDownstream };
1193 } else {
1194 result = { SkToS32(utf16Index + 1), kUpstream };
1195 }
1196 // For RTL we go another way
1197 return keepLooking = !run->leftToRight();
1198 }
1199
1200 if (dx >= context.clip.fRight + offsetX) {
1201 // We have to keep looking ; just in case keep the last one as the closest
1202 auto utf16Index = fOwner->getUTF16Index(context.run->globalClusterIndex(context.pos + context.size));
1203 if (run->leftToRight()) {
1204 result = {SkToS32(utf16Index), kUpstream};
1205 } else {
1206 result = {SkToS32(utf16Index), kDownstream};
1207 }
1208 // For RTL we go another way
1209 return keepLooking = run->leftToRight();
1210 }
1211
1212 // So we found the run that contains our coordinates
1213 // Find the glyph position in the run that is the closest left of our point
1214 // TODO: binary search
1215 size_t found = context.pos;
1216 for (size_t index = context.pos; index < context.pos + context.size; ++index) {
1217 // TODO: this rounding is done to match Flutter tests. Must be removed..
1218 auto end = littleRound(context.run->positionX(index) + context.fTextShift + offsetX);
1219 if (end > dx) {
1220 break;
1221 } else if (end == dx && !context.run->leftToRight()) {
1222 // When we move RTL variable end points to the beginning of the code point which is included
1223 break;
1224 }
1225 found = index;
1226 }
1227
1228 SkScalar glyphemePosLeft = context.run->positionX(found) + context.fTextShift + offsetX;
1229 SkScalar glyphemePosWidth = context.run->positionX(found + 1) - context.run->positionX(found);
1230
1231 // Find the grapheme range that contains the point
1232 auto clusterIndex8 = context.run->globalClusterIndex(found);
1233 auto clusterEnd8 = context.run->globalClusterIndex(found + 1);
1234
1235 SkScalar center = glyphemePosLeft + glyphemePosWidth / 2;
1236 if ((dx < center) == context.run->leftToRight()) {
1237 size_t utf16Index = fOwner->getUTF16Index(clusterIndex8);
1238 result = { SkToS32(utf16Index), kDownstream };
1239 } else {
1240 size_t utf16Index = fOwner->getUTF16Index(clusterEnd8);
1241 result = { SkToS32(utf16Index), kUpstream };
1242 }
1243
1244 return keepLooking = false;
1245
1246 });
1247 return keepLooking;
1248 }
1249 );
1250 return result;
1251 }
1252
getRectsForPlaceholders(std::vector<TextBox> & boxes)1253 void TextLine::getRectsForPlaceholders(std::vector<TextBox>& boxes) {
1254 this->iterateThroughVisualRuns(
1255 true,
1256 [&boxes, this](const Run* run, SkScalar runOffset, TextRange textRange,
1257 SkScalar* width) {
1258 auto context = this->measureTextInsideOneRun(textRange, run, runOffset, 0, true, false);
1259 *width = context.clip.width();
1260
1261 if (textRange.width() == 0) {
1262 return true;
1263 }
1264 if (!run->isPlaceholder()) {
1265 return true;
1266 }
1267
1268 SkRect clip = context.clip;
1269 clip.offset(this->offset());
1270
1271 clip.fLeft = littleRound(clip.fLeft);
1272 clip.fRight = littleRound(clip.fRight);
1273 clip.fTop = littleRound(clip.fTop);
1274 clip.fBottom = littleRound(clip.fBottom);
1275 boxes.emplace_back(clip, run->getTextDirection());
1276 return true;
1277 });
1278 }
1279 } // namespace textlayout
1280 } // namespace skia
1281