• 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 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