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