• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2019 Google LLC.
2 #include "modules/skparagraph/src/TextLine.h"
3 #include <unicode/brkiter.h>
4 #include <unicode/ubidi.h>
5 #include "modules/skparagraph/src/ParagraphImpl.h"
6 
7 #include "include/core/SkMaskFilter.h"
8 #include "include/effects/SkDashPathEffect.h"
9 #include "include/effects/SkDiscretePathEffect.h"
10 #include "src/core/SkMakeUnique.h"
11 
12 namespace skia {
13 namespace textlayout {
14 // TODO: deal with all the intersection functionality
intersectedSize(TextRange a,TextRange b)15 int32_t intersectedSize(TextRange a, TextRange b) {
16     if (a.empty() || b.empty()) {
17         return -1;
18     }
19     auto begin = SkTMax(a.start, b.start);
20     auto end = SkTMin(a.end, b.end);
21     return begin <= end ? SkToS32(end - begin) : -1;
22 }
23 
intersected(const TextRange & a,const TextRange & b)24 TextRange intersected(const TextRange& a, const TextRange& b) {
25     if (a.start == b.start && a.end == b.end) return a;
26     auto begin = SkTMax(a.start, b.start);
27     auto end = SkTMin(a.end, b.end);
28     return end >= begin ? TextRange(begin, end) : EMPTY_TEXT;
29 }
30 
31 SkTHashMap<SkFont, Run> TextLine::fEllipsisCache;
32 
TextLine(ParagraphImpl * master,SkVector offset,SkVector advance,BlockRange blocks,TextRange text,TextRange textWithSpaces,ClusterRange clusters,ClusterRange clustersWithGhosts,SkScalar widthWithSpaces,LineMetrics sizes)33 TextLine::TextLine(ParagraphImpl* master,
34                    SkVector offset,
35                    SkVector advance,
36                    BlockRange blocks,
37                    TextRange text,
38                    TextRange textWithSpaces,
39                    ClusterRange clusters,
40                    ClusterRange clustersWithGhosts,
41                    SkScalar widthWithSpaces,
42                    LineMetrics sizes)
43         : fMaster(master)
44         , fBlockRange(blocks)
45         , fTextRange(text)
46         , fTextWithWhitespacesRange(textWithSpaces)
47         , fClusterRange(clusters)
48         , fGhostClusterRange(clustersWithGhosts)
49         , fLogical()
50         , fAdvance(advance)
51         , fOffset(offset)
52         , fShift(0.0)
53         , fWidthWithSpaces(widthWithSpaces)
54         , fEllipsis(nullptr)
55         , fSizes(sizes)
56         , fHasBackground(false)
57         , fHasShadows(false)
58         , fHasDecorations(false) {
59     // Reorder visual runs
60     auto& start = master->cluster(fGhostClusterRange.start);
61     auto& end = master->cluster(fGhostClusterRange.end - 1);
62     size_t numRuns = end.runIndex() - start.runIndex() + 1;
63 
64     for (BlockIndex index = fBlockRange.start; index < fBlockRange.end; ++index) {
65         auto b = fMaster->styles().begin() + index;
66         if (b->fStyle.hasBackground()) {
67             fHasBackground = true;
68         }
69         if (b->fStyle.getDecorationType() != TextDecoration::kNoDecoration) {
70             fHasDecorations = true;
71         }
72         if (b->fStyle.getShadowNumber() > 0) {
73             fHasShadows = true;
74         }
75     }
76 
77     // Get the logical order
78     std::vector<UBiDiLevel> runLevels;
79     for (auto runIndex = start.runIndex(); runIndex <= end.runIndex(); ++runIndex) {
80         auto& run = fMaster->run(runIndex);
81         runLevels.emplace_back(run.fBidiLevel);
82     }
83 
84     std::vector<int32_t> logicalOrder(numRuns);
85     ubidi_reorderVisual(runLevels.data(), SkToU32(numRuns), logicalOrder.data());
86 
87     auto firstRunIndex = start.runIndex();
88     for (auto index : logicalOrder) {
89         fLogical.push_back(firstRunIndex + index);
90     }
91 }
92 
paint(SkCanvas * textCanvas)93 void TextLine::paint(SkCanvas* textCanvas) {
94     if (this->empty()) {
95         return;
96     }
97 
98     textCanvas->save();
99     textCanvas->translate(this->offset().fX, this->offset().fY);
100 
101     if (fHasBackground) {
102         this->iterateThroughStylesInTextOrder(
103             StyleType::kBackground,
104             [this, textCanvas](TextRange textRange, const TextStyle& style, SkScalar offsetX) {
105                 return this->paintBackground(textCanvas, textRange, style, offsetX);
106             });
107     }
108 
109     if (fHasShadows) {
110         this->iterateThroughStylesInTextOrder(
111             StyleType::kShadow,
112             [textCanvas, this](TextRange textRange, const TextStyle& style, SkScalar offsetX) {
113                 return this->paintShadow(textCanvas, textRange, style, offsetX);
114             });
115     }
116 
117     this->iterateThroughStylesInTextOrder(
118         StyleType::kForeground,
119         [textCanvas, this](TextRange textRange, const TextStyle& style, SkScalar offsetX) {
120             return this->paintText(textCanvas, textRange, style, offsetX);
121         });
122 
123     if (fHasDecorations) {
124         this->iterateThroughStylesInTextOrder(
125         StyleType::kDecorations,
126         [textCanvas, this](TextRange textRange, const TextStyle& style, SkScalar offsetX) {
127             return this->paintDecorations(textCanvas, textRange, style, offsetX);
128         });
129     }
130 
131     textCanvas->restore();
132 }
133 
format(TextAlign effectiveAlign,SkScalar maxWidth)134 void TextLine::format(TextAlign effectiveAlign, SkScalar maxWidth) {
135     SkScalar delta = maxWidth - this->width();
136     if (delta <= 0) {
137         return;
138     }
139 
140     if (effectiveAlign == TextAlign::kJustify) {
141         this->justify(maxWidth);
142     } else if (effectiveAlign == TextAlign::kRight) {
143         fShift = delta;
144     } else if (effectiveAlign == TextAlign::kCenter) {
145         fShift = delta / 2;
146     }
147 }
148 
assumedTextAlign() const149 TextAlign TextLine::assumedTextAlign() const {
150     if (this->fMaster->paragraphStyle().getTextAlign() != TextAlign::kJustify) {
151         return this->fMaster->paragraphStyle().effective_align();
152     }
153 
154     if (fClusterRange.empty()) {
155         return TextAlign::kLeft;
156     } else {
157         auto run = this->fMaster->cluster(fClusterRange.end - 1).run();
158         return run->leftToRight() ? TextAlign::kLeft : TextAlign::kRight;
159     }
160 }
161 
scanStyles(StyleType style,const StyleVisitor & visitor)162 void TextLine::scanStyles(StyleType style, const StyleVisitor& visitor) {
163     if (this->empty()) {
164         return;
165     }
166 
167     this->iterateThroughStylesInTextOrder(
168             style, [this, visitor](TextRange textRange, const TextStyle& style, SkScalar offsetX) {
169                 visitor(textRange, style, offsetX);
170                 return this->iterateThroughRuns(
171                         textRange, offsetX, false,
172                         [](Run*, int32_t, size_t, TextRange, SkRect, SkScalar, bool) { return true; });
173             });
174 }
175 
scanRuns(const RunVisitor & visitor)176 void TextLine::scanRuns(const RunVisitor& visitor) {
177     this->iterateThroughRuns(
178             fTextRange, 0, false,
179             [visitor](Run* run, int32_t pos, size_t size, TextRange text, SkRect clip, SkScalar sc, bool b) {
180                 visitor(run, pos, size, text, clip, sc, b);
181                 return true;
182             });
183 }
184 
paintText(SkCanvas * canvas,TextRange textRange,const TextStyle & style,SkScalar offsetX) const185 SkScalar TextLine::paintText(SkCanvas* canvas, TextRange textRange, const TextStyle& style,
186                              SkScalar offsetX) const {
187     SkPaint paint;
188     if (style.hasForeground()) {
189         paint = style.getForeground();
190     } else {
191         paint.setColor(style.getColor());
192     }
193 
194     auto shiftDown =  this->baseline();
195     return this->iterateThroughRuns(
196         textRange, offsetX, false,
197         [canvas, paint, shiftDown](Run* run, int32_t pos, size_t size, TextRange, SkRect clip, SkScalar shift, bool clippingNeeded) {
198             SkTextBlobBuilder builder;
199             run->copyTo(builder, SkToU32(pos), size, SkVector::Make(0, shiftDown));
200             canvas->save();
201             if (clippingNeeded) {
202                 canvas->clipRect(clip);
203             }
204             canvas->translate(shift, 0);
205             canvas->drawTextBlob(builder.make(), 0, 0, paint);
206             canvas->restore();
207             return true;
208         });
209 }
210 
paintBackground(SkCanvas * canvas,TextRange textRange,const TextStyle & style,SkScalar offsetX) const211 SkScalar TextLine::paintBackground(SkCanvas* canvas, TextRange textRange,
212                                    const TextStyle& style, SkScalar offsetX) const {
213     return this->iterateThroughRuns(textRange, offsetX, false,
214         [canvas, &style](Run* run, int32_t pos, size_t size, TextRange, SkRect clip,
215                         SkScalar shift, bool clippingNeeded) {
216             if (style.hasBackground()) {
217                 canvas->drawRect(clip, style.getBackground());
218             }
219             return true;
220         });
221 }
222 
paintShadow(SkCanvas * canvas,TextRange textRange,const TextStyle & style,SkScalar offsetX) const223 SkScalar TextLine::paintShadow(SkCanvas* canvas, TextRange textRange, const TextStyle& style,
224                                SkScalar offsetX) const {
225     auto shiftDown = this->baseline();
226     auto result = this->iterateThroughRuns(
227         textRange, offsetX, false,
228         [canvas, shiftDown, &style](Run* run, size_t pos, size_t size, TextRange, SkRect clip,
229                                     SkScalar shift, bool clippingNeeded) {
230             for (TextShadow shadow : style.getShadows()) {
231                 if (!shadow.hasShadow()) continue;
232 
233                 SkPaint paint;
234                 paint.setColor(shadow.fColor);
235                 if (shadow.fBlurRadius != 0.0) {
236                     auto filter = SkMaskFilter::MakeBlur(kNormal_SkBlurStyle,
237                                                          SkDoubleToScalar(shadow.fBlurRadius), false);
238                     paint.setMaskFilter(filter);
239                 }
240 
241                 SkTextBlobBuilder builder;
242                 run->copyTo(builder, pos, size, SkVector::Make(0, shiftDown));
243                 canvas->save();
244                 clip.offset(shadow.fOffset);
245                 if (clippingNeeded) {
246                     canvas->clipRect(clip);
247                 }
248                 canvas->translate(shift, 0);
249                 canvas->drawTextBlob(builder.make(), shadow.fOffset.x(), shadow.fOffset.y(),
250                                      paint);
251                 canvas->restore();
252             }
253             return true;
254         });
255 
256     return result;
257 }
258 
paintDecorations(SkCanvas * canvas,TextRange textRange,const TextStyle & style,SkScalar offsetX) const259 SkScalar TextLine::paintDecorations(SkCanvas* canvas, TextRange textRange,
260                                     const TextStyle& style, SkScalar offsetX) const {
261     return this->iterateThroughRuns(
262         textRange, offsetX, false,
263         [this, canvas, &style](Run* run, int32_t pos, size_t size, TextRange, SkRect clip, SkScalar shift,
264                               bool clippingNeeded) {
265             if (style.getDecorationType() == TextDecoration::kNoDecoration) {
266                 return true;
267             }
268 
269             for (auto decoration : AllTextDecorations) {
270                 if ((style.getDecorationType() & decoration) == 0) {
271                     continue;
272                 }
273 
274                 SkScalar thickness = style.getDecorationThicknessMultiplier();
275                 //
276                 SkScalar position = 0;
277                 switch (decoration) {
278                     case TextDecoration::kUnderline:
279                         position = -run->correctAscent() + thickness;
280                         break;
281                     case TextDecoration::kOverline:
282                         position = 0;
283                         break;
284                     case TextDecoration::kLineThrough: {
285                         position = (run->correctDescent() - run->correctAscent() - thickness) / 2;
286                         break;
287                     }
288                     default:
289                         // TODO: can we actually get here?
290                         SkASSERT(false);
291                         break;
292                 }
293 
294                 auto width = clip.width();
295                 SkScalar x = clip.left();
296                 SkScalar y = clip.top() + position;
297 
298                 // Decoration paint (for now) and/or path
299                 SkPaint paint;
300                 SkPath path;
301                 this->computeDecorationPaint(paint, clip, style, path);
302                 paint.setStrokeWidth(thickness);
303 
304                 switch (style.getDecorationStyle()) {
305                     case TextDecorationStyle::kWavy:
306                         path.offset(x, y);
307                         canvas->drawPath(path, paint);
308                         break;
309                     case TextDecorationStyle::kDouble: {
310                         canvas->drawLine(x, y, x + width, y, paint);
311                         SkScalar bottom = y + thickness * 2;
312                         canvas->drawLine(x, bottom, x + width, bottom, paint);
313                         break;
314                     }
315                     case TextDecorationStyle::kDashed:
316                     case TextDecorationStyle::kDotted:
317                     case TextDecorationStyle::kSolid:
318                         canvas->drawLine(x, y, x + width, y, paint);
319                         break;
320                     default:
321                         break;
322                 }
323             }
324 
325             return true;
326         });
327 }
328 
computeDecorationPaint(SkPaint & paint,SkRect clip,const TextStyle & style,SkPath & path) const329 void TextLine::computeDecorationPaint(SkPaint& paint,
330                                       SkRect clip,
331                                       const TextStyle& style,
332                                       SkPath& path) const {
333     paint.setStyle(SkPaint::kStroke_Style);
334     if (style.getDecorationColor() == SK_ColorTRANSPARENT) {
335         paint.setColor(style.getColor());
336     } else {
337         paint.setColor(style.getDecorationColor());
338     }
339 
340     SkScalar scaleFactor = style.getFontSize() / 14.f;
341 
342     switch (style.getDecorationStyle()) {
343         case TextDecorationStyle::kSolid:
344             break;
345 
346         case TextDecorationStyle::kDouble:
347             break;
348 
349             // Note: the intervals are scaled by the thickness of the line, so it is
350             // possible to change spacing by changing the decoration_thickness
351             // property of TextStyle.
352         case TextDecorationStyle::kDotted: {
353             const SkScalar intervals[] = {1.0f * scaleFactor, 1.5f * scaleFactor,
354                                           1.0f * scaleFactor, 1.5f * scaleFactor};
355             size_t count = sizeof(intervals) / sizeof(intervals[0]);
356             paint.setPathEffect(SkPathEffect::MakeCompose(
357                     SkDashPathEffect::Make(intervals, (int32_t)count, 0.0f),
358                     SkDiscretePathEffect::Make(0, 0)));
359             break;
360         }
361             // Note: the intervals are scaled by the thickness of the line, so it is
362             // possible to change spacing by changing the decoration_thickness
363             // property of TextStyle.
364         case TextDecorationStyle::kDashed: {
365             const SkScalar intervals[] = {4.0f * scaleFactor, 2.0f * scaleFactor,
366                                           4.0f * scaleFactor, 2.0f * scaleFactor};
367             size_t count = sizeof(intervals) / sizeof(intervals[0]);
368             paint.setPathEffect(SkPathEffect::MakeCompose(
369                     SkDashPathEffect::Make(intervals, (int32_t)count, 0.0f),
370                     SkDiscretePathEffect::Make(0, 0)));
371             break;
372         }
373         case TextDecorationStyle::kWavy: {
374             int wave_count = 0;
375             SkScalar x_start = 0;
376             SkScalar wavelength = scaleFactor * style.getDecorationThicknessMultiplier();
377             auto width = clip.width();
378             path.moveTo(0, 0);
379             while (x_start + wavelength * 2 < width) {
380                 path.rQuadTo(wavelength,
381                              wave_count % 2 != 0 ? wavelength : -wavelength,
382                              wavelength * 2,
383                              0);
384                 x_start += wavelength * 2;
385                 ++wave_count;
386             }
387             break;
388         }
389     }
390 }
391 
justify(SkScalar maxWidth)392 void TextLine::justify(SkScalar maxWidth) {
393     // Count words and the extra spaces to spread across the line
394     // TODO: do it at the line breaking?..
395     size_t whitespacePatches = 0;
396     SkScalar textLen = 0;
397     bool whitespacePatch = false;
398     this->iterateThroughClustersInGlyphsOrder(false, false,
399         [&whitespacePatches, &textLen, &whitespacePatch](const Cluster* cluster, ClusterIndex index, bool leftToRight, bool ghost) {
400             if (cluster->isWhitespaces()) {
401                 if (!whitespacePatch) {
402                     whitespacePatch = true;
403                     ++whitespacePatches;
404                 }
405             } else {
406                 whitespacePatch = false;
407             }
408             textLen += cluster->width();
409             return true;
410         });
411 
412     if (whitespacePatches == 0) {
413         return;
414     }
415 
416     SkScalar step = (maxWidth - textLen) / whitespacePatches;
417     SkScalar shift = 0;
418 
419     // Deal with the ghost spaces
420     auto ghostShift = maxWidth - this->fAdvance.fX;
421     // Spread the extra whitespaces
422     whitespacePatch = false;
423     this->iterateThroughClustersInGlyphsOrder(false, true, [&](const Cluster* cluster, ClusterIndex index, bool leftToRight, bool ghost) {
424 
425         if (ghost) {
426             if (leftToRight) {
427                 fMaster->shiftCluster(index, ghostShift);
428             }
429             return true;
430         }
431 
432         if (cluster->isWhitespaces()) {
433             if (!whitespacePatch) {
434                 shift += step;
435                 whitespacePatch = true;
436                 --whitespacePatches;
437             }
438         } else {
439             whitespacePatch = false;
440         }
441         fMaster->shiftCluster(index, shift);
442         return true;
443     });
444 
445     SkAssertResult(SkScalarNearlyEqual(shift, maxWidth - textLen));
446     SkASSERT(whitespacePatches == 0);
447 
448     this->fWidthWithSpaces += ghostShift;
449     this->fAdvance.fX = maxWidth;
450 }
451 
createEllipsis(SkScalar maxWidth,const SkString & ellipsis,bool)452 void TextLine::createEllipsis(SkScalar maxWidth, const SkString& ellipsis, bool) {
453     // Replace some clusters with the ellipsis
454     // Go through the clusters in the reverse logical order
455     // taking off cluster by cluster until the ellipsis fits
456     SkScalar width = fAdvance.fX;
457     iterateThroughClustersInGlyphsOrder(
458         true, false, [this, &width, ellipsis, maxWidth](const Cluster* cluster, ClusterIndex index, bool leftToRight, bool ghost) {
459             if (cluster->isWhitespaces()) {
460                 width -= cluster->width();
461                 return true;
462             }
463 
464             // Shape the ellipsis
465             Run* cached = fEllipsisCache.find(cluster->font());
466             if (cached == nullptr) {
467                 cached = shapeEllipsis(ellipsis, cluster->run());
468             } else {
469                 cached->setMaster(fMaster);
470             }
471             fEllipsis = std::make_shared<Run>(*cached);
472 
473             // See if it fits
474             if (width + fEllipsis->advance().fX > maxWidth) {
475                 width -= cluster->width();
476                 // Continue if it's not
477                 return true;
478             }
479 
480             fEllipsis->shift(width, 0);
481             fAdvance.fX = width;
482             return false;
483         });
484 }
485 
shapeEllipsis(const SkString & ellipsis,Run * run)486 Run* TextLine::shapeEllipsis(const SkString& ellipsis, Run* run) {
487 
488     class ShapeHandler final : public SkShaper::RunHandler {
489     public:
490         ShapeHandler(SkScalar lineHeight, const SkString& ellipsis)
491             : fRun(nullptr), fLineHeight(lineHeight), fEllipsis(ellipsis) {}
492         Run* run() { return fRun; }
493 
494     private:
495         void beginLine() override {}
496 
497         void runInfo(const RunInfo&) override {}
498 
499         void commitRunInfo() override {}
500 
501         Buffer runBuffer(const RunInfo& info) override {
502             fRun = fEllipsisCache.set(info.fFont,
503                                       Run(nullptr, info, fLineHeight, 0, 0));
504             return fRun->newRunBuffer();
505         }
506 
507         void commitRunBuffer(const RunInfo& info) override {
508             fRun->fAdvance.fX = info.fAdvance.fX;
509             fRun->fAdvance.fY = fRun->advance().fY;
510         }
511 
512         void commitLine() override {}
513 
514         Run* fRun;
515         SkScalar fLineHeight;
516         SkString fEllipsis;
517     };
518 
519     ShapeHandler handler(run->lineHeight(), ellipsis);
520     std::unique_ptr<SkShaper> shaper = SkShaper::MakeShapeDontWrapOrReorder();
521     SkASSERT_RELEASE(shaper != nullptr);
522     shaper->shape(ellipsis.c_str(), ellipsis.size(), run->font(), true,
523                   std::numeric_limits<SkScalar>::max(), &handler);
524     handler.run()->fTextRange = TextRange(0, ellipsis.size());
525     handler.run()->fMaster = fMaster;
526     return handler.run();
527 }
528 
measureTextInsideOneRun(TextRange textRange,Run * run,size_t & pos,size_t & size,bool includeGhostSpaces,bool & clippingNeeded) const529 SkRect TextLine::measureTextInsideOneRun(
530         TextRange textRange, Run* run, size_t& pos, size_t& size, bool includeGhostSpaces, bool& clippingNeeded) const {
531 
532     SkASSERT(intersectedSize(run->textRange(), textRange) >= 0);
533 
534     // Find [start:end] clusters for the text
535     bool found;
536     ClusterIndex startIndex;
537     ClusterIndex endIndex;
538     std::tie(found, startIndex, endIndex) = run->findLimitingClusters(textRange);
539     if (!found) {
540         SkASSERT(textRange.empty());
541         return SkRect::MakeEmpty();
542     }
543 
544     auto start = fMaster->clusters().begin() + startIndex;
545     auto end = fMaster->clusters().begin() + endIndex;
546     pos = start->startPos();
547     size = end->endPos() - start->startPos();
548 
549     // Calculate the clipping rectangle for the text with cluster edges
550     // There are 2 cases:
551     // EOL (when we expect the last cluster clipped without any spaces)
552     // Anything else (when we want the cluster width contain all the spaces -
553     // coming from letter spacing or word spacing or justification)
554     auto range = includeGhostSpaces ? fGhostClusterRange : fClusterRange;
555     bool needsClipping = (run->leftToRight() ? endIndex == range.end  - 1 : startIndex == range.end - 1);
556     SkRect clip =
557             SkRect::MakeXYWH(run->positionX(start->startPos()) - run->positionX(0),
558                              sizes().runTop(run),
559                              run->calculateWidth(start->startPos(), end->endPos(), needsClipping),
560                              run->calculateHeight());
561 
562     // Correct the width in case the text edges don't match clusters
563     // TODO: This is where we get smart about selecting a part of a cluster
564     //  by shaping each grapheme separately and then use the result sizes
565     //  to calculate the proportions
566     auto leftCorrection = start->sizeToChar(textRange.start);
567     auto rightCorrection = end->sizeFromChar(textRange.end - 1);
568     clip.fLeft += leftCorrection;
569     clip.fRight -= rightCorrection;
570     clippingNeeded = leftCorrection != 0 || rightCorrection != 0;
571 
572     // SkDebugf("measureTextInsideOneRun: '%s'[%d:%d]\n", text.begin(), pos, pos + size);
573 
574     return clip;
575 }
576 
iterateThroughClustersInGlyphsOrder(bool reverse,bool includeGhosts,const ClustersVisitor & visitor) const577 void TextLine::iterateThroughClustersInGlyphsOrder(bool reverse,
578                                                    bool includeGhosts,
579                                                    const ClustersVisitor& visitor) const {
580     // Walk through the clusters in the logical order (or reverse)
581     for (size_t r = 0; r != fLogical.size(); ++r) {
582         auto& runIndex = fLogical[reverse ? fLogical.size() - r - 1 : r];
583         auto run = this->fMaster->runs().begin() + runIndex;
584         auto start = SkTMax(run->clusterRange().start, fClusterRange.start);
585         auto end = SkTMin(run->clusterRange().end, fClusterRange.end);
586         auto ghosts = SkTMin(run->clusterRange().end, fGhostClusterRange.end);
587 
588         if (run->leftToRight() != reverse) {
589             for (auto index = start; index < ghosts; ++index) {
590                 if (index >= end && !includeGhosts) {
591                     break;
592                 }
593                 const auto& cluster = &fMaster->cluster(index);
594                 if (!visitor(cluster, index, run->leftToRight(), index >= end)) {
595                     return;
596                 }
597             }
598         } else {
599             for (auto index = ghosts; index > start; --index) {
600                 if (index > end && !includeGhosts) {
601                     continue;
602                 }
603                 const auto& cluster = &fMaster->cluster(index - 1);
604                 if (!visitor(cluster, index - 1, run->leftToRight(), index > end)) {
605                     return;
606                 }
607             }
608         }
609     }
610 }
611 
calculateLeftVisualOffset(TextRange textRange) const612 SkScalar TextLine::calculateLeftVisualOffset(TextRange textRange) const {
613     SkScalar partOfTheCurrentRun = 0;
614     return this->iterateThroughRuns(this->textWithSpaces(), 0, true,
615         [textRange, &partOfTheCurrentRun, this](
616                 Run* run, size_t pos, size_t size, TextRange text,
617                 SkRect clip, SkScalar shift, bool clippingNeeded) {
618             if (text.start > textRange.start || text.end <= textRange.start) {
619                 // This run does not even touch the text start
620             } else {
621                 // This is the run
622                 TextRange part;
623                 if (run->leftToRight()) {
624                     part = {text.start, textRange.start};
625                 } else if (textRange.end < text.end) {
626                     part = {textRange.end, text.end};
627                 }
628                 if (part.width() == 0) {
629                     return false;
630                 }
631                 size_t pos;
632                 size_t size;
633                 bool clippingNeeded;
634                 SkRect partClip = this->measureTextInsideOneRun(part, run, pos, size, true, clippingNeeded);
635                 partOfTheCurrentRun = partClip.width();
636                 return false;
637             }
638 
639             return true;
640         }) +
641            partOfTheCurrentRun;
642 }
643 
644 // Walk through the runs in the logical order
iterateThroughRuns(TextRange textRange,SkScalar runOffset,bool includeGhostWhitespaces,const RunVisitor & visitor) const645 SkScalar TextLine::iterateThroughRuns(TextRange textRange,
646                                       SkScalar runOffset,
647                                       bool includeGhostWhitespaces,
648                                       const RunVisitor& visitor) const {
649     TRACE_EVENT0("skia", TRACE_FUNC);
650 
651     SkScalar width = 0;
652     for (auto& runIndex : fLogical) {
653         const auto run = &this->fMaster->run(runIndex);
654         // Only skip the text if it does not even touch the run
655         auto intersection = intersectedSize(run->textRange(), textRange);
656         if (intersection < 0 || (intersection == 0 && textRange.end != run->textRange().end)) {
657             continue;
658         }
659 
660         auto intersect = intersected(run->textRange(), textRange);
661         if (run->textRange().empty() || intersect.empty()) {
662             continue;
663         }
664 
665         // Measure the text
666         size_t pos;
667         size_t size;
668         bool clippingNeeded;
669         SkRect clip = this->measureTextInsideOneRun(intersect, run, pos, size, includeGhostWhitespaces, clippingNeeded);
670         if (clip.height() == 0) {
671             continue;
672         }
673 
674         // Clip the text
675         auto shift = runOffset - clip.fLeft;
676         clip.offset(shift, 0);
677         if (includeGhostWhitespaces) {
678             clippingNeeded = false;
679         } else {
680             clippingNeeded = true;
681             if (clip.fRight > fAdvance.fX) {
682                 clip.fRight = fAdvance.fX;
683             }
684         }
685 
686         if (!visitor(run, pos, size, intersect, clip, shift - run->positionX(0), clippingNeeded)) {
687             return width;
688         }
689 
690         if (run->leftToRight() || &runIndex == &fLogical.back()) {
691             width += clip.width();
692             runOffset += clip.width();
693         } else {
694             width += run->advance().fX;
695             runOffset += run->advance().fX;
696         }
697 
698     }
699 
700     if (this->ellipsis() != nullptr) {
701         auto ellipsis = this->ellipsis();
702         if (!visitor(ellipsis, 0, ellipsis->size(), ellipsis->textRange(), ellipsis->clip(), ellipsis->clip().fLeft,
703                      false)) {
704             return width;
705         }
706         width += ellipsis->clip().width();
707     }
708 
709     return width;
710 }
711 
iterateThroughStylesInTextOrder(StyleType styleType,const StyleVisitor & visitor) const712 void TextLine::iterateThroughStylesInTextOrder(StyleType styleType,
713                                                const StyleVisitor& visitor) const {
714 
715     TextIndex start = EMPTY_INDEX;
716     size_t size = 0;
717     const TextStyle* prevStyle = nullptr;
718 
719     SkScalar offsetX = 0;
720     for (BlockIndex index = fBlockRange.start; index < fBlockRange.end; ++index) {
721         auto block = fMaster->styles().begin() + index;
722         auto intersect = intersected(block->fRange, this->trimmedText());
723         if (intersect.empty()) {
724             if (start == EMPTY_INDEX) {
725                 // This style is not applicable to the line
726                 continue;
727             } else {
728                 // We have found all the good styles already
729                 break;
730             }
731         }
732 
733         auto* style = &block->fStyle;
734         if (start != EMPTY_INDEX && style->matchOneAttribute(styleType, *prevStyle)) {
735             size += intersect.width();
736             continue;
737         } else if (start == EMPTY_INDEX ) {
738             // First time only
739             prevStyle = style;
740             size = intersect.width();
741             start = intersect.start;
742             continue;
743         }
744 
745         auto width = visitor(TextRange(start, start + size), *prevStyle, offsetX);
746         offsetX += width;
747 
748         // Start all over again
749         prevStyle = style;
750         start = intersect.start;
751         size = intersect.width();
752     }
753 
754     if (prevStyle == nullptr) return;
755 
756     // The very last style
757     auto width = visitor(TextRange(start, start + size), *prevStyle, offsetX);
758     offsetX += width;
759 
760     // This is a very important assert!
761     // It asserts that 2 different ways of calculation come with the same results
762     if (!SkScalarNearlyEqual(offsetX, this->width())) {
763         SkDebugf("ASSERT: %f != %f\n", offsetX, this->width());
764     }
765     SkASSERT(SkScalarNearlyEqual(offsetX, this->width()));
766 }
767 
offset() const768 SkVector TextLine::offset() const {
769     return fOffset + SkVector::Make(fShift, 0);
770 }
771 }  // namespace textlayout
772 }  // namespace skia
773