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