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