• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2019 Google LLC.
2 #include "modules/skparagraph/src/ParagraphImpl.h"
3 #include "modules/skparagraph/src/TextWrapper.h"
4 
5 #ifdef ENABLE_TEXT_ENHANCE
6 #include <cstdint>
7 #include <iterator>
8 #include <limits>
9 #include <memory>
10 #include <numeric>
11 
12 #include "include/DartTypes.h"
13 #include "log.h"
14 #include "modules/skparagraph/include/Hyphenator.h"
15 #include "modules/skparagraph/src/TextTabAlign.h"
16 #include "TextParameter.h"
17 #endif
18 
19 namespace skia {
20 namespace textlayout {
21 
22 namespace {
23 #ifdef ENABLE_TEXT_ENHANCE
24 const size_t BREAK_NUM_TWO = 2;
25 #endif
26 struct LineBreakerWithLittleRounding {
LineBreakerWithLittleRoundingskia::textlayout::__anon98537c470111::LineBreakerWithLittleRounding27     LineBreakerWithLittleRounding(SkScalar maxWidth, bool applyRoundingHack)
28         : fLower(maxWidth - 0.25f)
29         , fMaxWidth(maxWidth)
30         , fUpper(maxWidth + 0.25f)
31         , fApplyRoundingHack(applyRoundingHack) {}
32 
breakLineskia::textlayout::__anon98537c470111::LineBreakerWithLittleRounding33     bool breakLine(SkScalar width) const {
34         if (width < fLower) {
35             return false;
36         } else if (width > fUpper) {
37             return true;
38         }
39 
40         auto val = std::fabs(width);
41         SkScalar roundedWidth;
42         if (fApplyRoundingHack) {
43             if (val < 10000) {
44                 roundedWidth = SkScalarRoundToScalar(width * 100) * (1.0f/100);
45             } else if (val < 100000) {
46                 roundedWidth = SkScalarRoundToScalar(width *  10) * (1.0f/10);
47             } else {
48                 roundedWidth = SkScalarFloorToScalar(width);
49             }
50         } else {
51             if (val < 10000) {
52                 roundedWidth = SkScalarFloorToScalar(width * 100) * (1.0f/100);
53             } else if (val < 100000) {
54                 roundedWidth = SkScalarFloorToScalar(width *  10) * (1.0f/10);
55             } else {
56                 roundedWidth = SkScalarFloorToScalar(width);
57             }
58         }
59         return roundedWidth > fMaxWidth;
60     }
61 
62     const SkScalar fLower, fMaxWidth, fUpper;
63     const bool fApplyRoundingHack;
64 };
65 }  // namespace
66 
67 #ifdef ENABLE_TEXT_ENHANCE
matchHyphenResult(const std::vector<uint8_t> & result,ParagraphImpl * owner,size_t & pos,SkScalar maxWidth,SkScalar len)68 void TextWrapper::matchHyphenResult(const std::vector<uint8_t>& result, ParagraphImpl* owner, size_t& pos,
69                                     SkScalar maxWidth, SkScalar len)
70 {
71     auto startPos = pos;
72     size_t ix = 0;
73 
74     // Result array may have more code points than we have visible clusters
75     int32_t prevIx = -1;
76     // cumulatively iterate width vs breaks
77     for (const auto& breakPos : result) {
78         int32_t clusterIx = static_cast<int32_t>(owner->fClustersIndexFromCodeUnit[startPos + ix]);
79         if (clusterIx == prevIx) {
80             ++ix;
81             continue;
82         }
83         prevIx = clusterIx;
84         TEXT_LOGD("hyphen break width:%{public}f / %{public}f : %{public}f", len, maxWidth,
85                   owner->cluster(clusterIx).width());
86         len += owner->cluster(clusterIx).width();
87         auto shouldBreak = (len > maxWidth);
88         if (breakPos & 0x1) {
89             // we need to break after previous char, but the result needs to be mapped
90             pos = startPos + ix;
91         }
92         ++ix;
93         if (shouldBreak) {
94             break;
95         }
96     }
97 }
98 
tryBreakWord(Cluster * startCluster,Cluster * endOfClusters,SkScalar widthBeforeCluster,SkScalar maxWidth)99 size_t TextWrapper::tryBreakWord(Cluster *startCluster, Cluster *endOfClusters,
100                                  SkScalar widthBeforeCluster, SkScalar maxWidth)
101 {
102     auto startPos = startCluster->textRange().start;
103     auto endPos = startPos;
104     auto owner = startCluster->getOwner();
105     for (auto next = startCluster + 1; next != endOfClusters; next++) {
106         // find the end boundary of current word (hard/soft/whitespace break)
107         if (next->isWhitespaceBreak() || next->isHardBreak()) {
108             break;
109         } else {
110             endPos = next->textRange().end;
111         }
112     }
113 
114     // Using end position cluster height as an estimate for reserved hyphen width
115     // hyphen may not be shaped at this point. End pos may be non-drawable, so prefer
116     // previous glyph before break
117     auto mappedEnd = owner->fClustersIndexFromCodeUnit[endPos];
118     auto len = widthBeforeCluster + owner->cluster(mappedEnd > 0 ? mappedEnd - 1 : 0).height();
119     // ToDo: We can stop here based on hyhpenation frequency setting
120     // but there is no need to proceed with hyphenation if we don't have space for even hyphen
121     if (std::isnan(len) || len >= maxWidth) {
122         return startCluster->textRange().start;
123     }
124 
125     auto locale = owner->paragraphStyle().getTextStyle().getLocale();
126     auto result = Hyphenator::getInstance().findBreakPositions(locale, owner->fText, startPos, endPos);
127     endPos = startPos;
128     matchHyphenResult(result, owner, endPos, maxWidth, len);
129 
130     return endPos;
131 }
132 
lookAheadByHyphen(Cluster * endOfClusters,SkScalar widthBeforeCluster,SkScalar maxWidth)133 bool TextWrapper::lookAheadByHyphen(Cluster* endOfClusters, SkScalar widthBeforeCluster, SkScalar maxWidth)
134 {
135     auto startCluster = fClusters.startCluster();
136     while (startCluster != endOfClusters && startCluster->isWhitespaceBreak()) {
137         ++startCluster;
138     }
139     if (startCluster == endOfClusters) {
140         return false;
141     }
142     auto endPos = tryBreakWord(startCluster, endOfClusters, widthBeforeCluster - fClusters.width(), maxWidth);
143     // if break position found, set fClusters and fWords accrodingly and break
144     if (endPos > startCluster->textRange().start) {
145         // need to break before the mapped end cluster
146         auto owner = startCluster->getOwner();
147         fClusters = TextStretch(startCluster, &owner->cluster(owner->fClustersIndexFromCodeUnit[endPos]) - 1,
148                                 fClusters.metrics().getForceStrut());
149         fWords.extend(fClusters);
150         fBrokeLineWithHyphen = true;
151         return false;
152     }
153     // else let the existing implementation do its best efforts
154     return true;
155 }
156 
157 // Since we allow cluster clipping when they don't fit
158 // we have to work with stretches - parts of clusters
lookAhead(SkScalar maxWidth,Cluster * endOfClusters,bool applyRoundingHack,WordBreakType wordBreakType,bool needEllipsis)159 void TextWrapper::lookAhead(SkScalar maxWidth, Cluster* endOfClusters, bool applyRoundingHack,
160                             WordBreakType wordBreakType, bool needEllipsis) {
161 
162     reset();
163     fEndLine.metrics().clean();
164     fWords.startFrom(fEndLine.startCluster(), fEndLine.startPos());
165     fClusters.startFrom(fEndLine.startCluster(), fEndLine.startPos());
166     fClip.startFrom(fEndLine.startCluster(), fEndLine.startPos());
167 
168     bool isFirstWord = true;
169     TextTabAlign textTabAlign(endOfClusters->getOwner()->paragraphStyle().getTextTab());
170     textTabAlign.init(maxWidth, endOfClusters);
171 
172     LineBreakerWithLittleRounding breaker(maxWidth, applyRoundingHack);
173     Cluster* nextNonBreakingSpace = nullptr;
174     SkScalar totalFakeSpacing = 0.0;
175     bool attemptedHyphenate = false;
176 
177     for (auto cluster = fEndLine.endCluster(); cluster < endOfClusters; ++cluster) {
178         totalFakeSpacing += (cluster->needAutoSpacing() && cluster != fEndLine.endCluster()) ?
179             (cluster - 1)->getFontSize() / AUTO_SPACING_WIDTH_RATIO : 0;
180         SkScalar widthBeforeCluster = fWords.width() + fClusters.width() + totalFakeSpacing;
181         if (cluster->isHardBreak()) {
182             if (cluster != fEndLine.endCluster()) {
183                 isFirstWord = false;
184             }
185         } else if (
186                 // TODO: Trying to deal with flutter rounding problem. Must be removed...
187                 SkScalar width = cluster->width() + widthBeforeCluster;
188                 (!isFirstWord || wordBreakType != WordBreakType::NORMAL) &&
189                 breaker.breakLine(width)) {
190             // if the hyphenator has already run as balancing algorithm, use the cluster information
191             if (cluster->isHyphenBreak() && !needEllipsis) {
192                 // we dont want to add the current cluster as the hyphenation algorithm marks breaks before a cluster
193                 // however, if we cannot fit anything to a line, we need to break out here
194                 if (fWords.empty() && fClusters.empty()) {
195                     fClusters.extend(cluster);
196                     fTooLongCluster = true;
197                     break;
198                 }
199                 if (!fClusters.empty()) {
200                     fWords.extend(fClusters);
201                     fBrokeLineWithHyphen = true;
202                     break;
203                 }
204                 // let hyphenator try before this if it is enabled
205             } else if (cluster->isWhitespaceBreak() && ((wordBreakType != WordBreakType::BREAK_HYPHEN) ||
206                        (wordBreakType == WordBreakType::BREAK_HYPHEN && attemptedHyphenate && !needEllipsis))) {
207                 // It's the end of the word
208                 isFirstWord = false;
209                 fClusters.extend(cluster);
210 
211                 bool tabAlignRet = false;
212                 if (cluster->isTabulation()) {
213                     tabAlignRet = textTabAlign.processTab(fWords, fClusters, cluster, totalFakeSpacing);
214                 } else {
215                     tabAlignRet = textTabAlign.processEndofWord(fWords, fClusters, cluster, totalFakeSpacing);
216                 }
217                 if (tabAlignRet) {
218                     break;
219                 }
220                 fMinIntrinsicWidth = std::max(fMinIntrinsicWidth, this->getClustersTrimmedWidth());
221                 fWords.extend(fClusters);
222                 continue;
223             } else if (cluster->run().isPlaceholder()) {
224                 isFirstWord = false;
225                 if (!fClusters.empty()) {
226                     // Placeholder ends the previous word
227                     fMinIntrinsicWidth = std::max(fMinIntrinsicWidth, this->getClustersTrimmedWidth());
228                     fWords.extend(fClusters);
229                 }
230 
231                 if (cluster->width() > maxWidth && fWords.empty()) {
232                     // Placeholder is the only text and it's longer than the line;
233                     // it does not count in fMinIntrinsicWidth
234                     fClusters.extend(cluster);
235                     fTooLongCluster = true;
236                     fTooLongWord = true;
237                 } else {
238                     // Placeholder does not fit the line; it will be considered again on the next line
239                 }
240                 break;
241             }
242 
243             // should do this only if hyphenation is enabled
244             if (wordBreakType == WordBreakType::BREAK_HYPHEN && !attemptedHyphenate && !fClusters.empty() &&
245                 !needEllipsis) {
246                 attemptedHyphenate = true;
247                 if (!lookAheadByHyphen(endOfClusters, widthBeforeCluster, breaker.fUpper)) {
248                     break;
249                 }
250             }
251 
252             textTabAlign.processEndofLine(fWords, fClusters, cluster, totalFakeSpacing);
253 
254             // Walk further to see if there is a too long word, cluster or glyph
255             SkScalar nextWordLength = fClusters.width();
256             SkScalar nextShortWordLength = nextWordLength;
257             for (auto further = cluster; further != endOfClusters; ++further) {
258                 if (further->isSoftBreak() || further->isHardBreak() || further->isWhitespaceBreak()) {
259                     break;
260                 }
261                 if (further->run().isPlaceholder()) {
262                   // Placeholder ends the word
263                   break;
264                 }
265 
266                 if (nextWordLength > 0 && nextWordLength <= maxWidth && further->isIntraWordBreak()) {
267                     // The cluster is spaces but not the end of the word in a normal sense
268                     nextNonBreakingSpace = further;
269                     nextShortWordLength = nextWordLength;
270                 }
271 
272                 if (maxWidth == 0) {
273                     // This is a tricky flutter case: layout(width:0) places 1 cluster on each line
274                     nextWordLength = std::max(nextWordLength, further->width());
275                 } else {
276                     nextWordLength += further->width();
277                 }
278             }
279             if (nextWordLength > maxWidth) {
280                 if (nextNonBreakingSpace != nullptr) {
281                     // We only get here if the non-breaking space improves our situation
282                     // (allows us to break the text to fit the word)
283                     if (SkScalar shortLength = fWords.width() + nextShortWordLength;
284                         !breaker.breakLine(shortLength)) {
285                         // We can add the short word to the existing line
286                         fClusters = TextStretch(fClusters.startCluster(), nextNonBreakingSpace, fClusters.metrics().getForceStrut());
287                         fMinIntrinsicWidth = std::max(fMinIntrinsicWidth, nextShortWordLength);
288                         fWords.extend(fClusters);
289                     } else {
290                         // We can place the short word on the next line
291                         fClusters.clean();
292                     }
293                     // Either way we are not in "word is too long" situation anymore
294                     break;
295                 }
296                 // If the word is too long we can break it right now and hope it's enough
297                 fMinIntrinsicWidth = std::max(fMinIntrinsicWidth, nextWordLength);
298                 if (fClusters.endPos() - fClusters.startPos() > 1 ||
299                     fWords.empty()) {
300                     fTooLongWord = true;
301                 } else {
302                     // Even if the word is too long there is a very little space on this line.
303                     // let's deal with it on the next line.
304                 }
305             }
306 
307             if (fWords.empty() && breaker.breakLine(cluster->width())) {
308                 fClusters.extend(cluster);
309                 fTooLongCluster = true;
310                 fTooLongWord = true;
311             }
312             break;
313         }
314 
315         if (cluster->isSoftBreak() || cluster->isWhitespaceBreak()) {
316             isFirstWord = false;
317         }
318 
319         if (cluster->run().isPlaceholder()) {
320             if (!fClusters.empty()) {
321                 // Placeholder ends the previous word (placeholders are ignored in trimming)
322                 fMinIntrinsicWidth = std::max(fMinIntrinsicWidth, getClustersTrimmedWidth());
323                 fWords.extend(fClusters);
324             }
325 
326             // Placeholder is separate word and its width now is counted in minIntrinsicWidth
327             fMinIntrinsicWidth = std::max(fMinIntrinsicWidth, cluster->width());
328             fWords.extend(cluster);
329         } else {
330             if (cluster->isTabulation()) {
331                 if (textTabAlign.processTab(fWords, fClusters, cluster, totalFakeSpacing)) {
332                     break;
333                 }
334                 fClusters.extend(cluster);
335 
336                 fMinIntrinsicWidth = std::max(fMinIntrinsicWidth, getClustersTrimmedWidth());
337                 fWords.extend(fClusters);
338             } else {
339                 fClusters.extend(cluster);
340                 if (fClusters.endOfWord()) { // Keep adding clusters/words
341                     if (textTabAlign.processEndofWord(fWords, fClusters, cluster, totalFakeSpacing)) {
342                         if (wordBreakType == WordBreakType::BREAK_ALL) {
343                             fClusters.trim(cluster);
344                         }
345                         break;
346                     }
347 
348                     fMinIntrinsicWidth = std::max(fMinIntrinsicWidth, getClustersTrimmedWidth());
349                     fWords.extend(fClusters);
350                 } else {
351                     if (textTabAlign.processCluster(fWords, fClusters, cluster, totalFakeSpacing)) {
352                         fClusters.trim(cluster);
353                         break;
354                     }
355                 }
356             }
357         }
358 
359         if ((fHardLineBreak = cluster->isHardBreak())) {
360             // Stop at the hard line break
361             break;
362         }
363     }
364 }
365 
moveForward(bool hasEllipsis,bool breakAll)366 void TextWrapper::moveForward(bool hasEllipsis, bool breakAll) {
367 
368     // We normally break lines by words.
369     // The only way we may go to clusters is if the word is too long or
370     // it's the first word and it has an ellipsis attached to it.
371     // If nothing fits we show the clipping.
372     fTooLongWord = breakAll;
373     if (!fWords.empty()) {
374         fEndLine.extend(fWords);
375 #ifdef SK_IGNORE_SKPARAGRAPH_ELLIPSIS_FIX
376         if (!fTooLongWord || hasEllipsis) { // Ellipsis added to a word
377 #else
378         if (!fTooLongWord && !hasEllipsis) { // Ellipsis added to a grapheme
379 #endif
380             return;
381         }
382     }
383     if (!fClusters.empty()) {
384         fEndLine.extend(fClusters);
385         if (!fTooLongCluster) {
386             return;
387         }
388     }
389 
390     if (!fClip.empty()) {
391         // Flutter: forget the clipped cluster but keep the metrics
392         fEndLine.metrics().add(fClip.metrics());
393     }
394 }
395 
396 // Special case for start/end cluster since they can be clipped
397 void TextWrapper::trimEndSpaces(TextAlign align) {
398     // Remember the breaking position
399     fEndLine.saveBreak();
400     // Skip all space cluster at the end
401     for (auto cluster = fEndLine.endCluster();
402          cluster >= fEndLine.startCluster() && cluster->isWhitespaceBreak();
403          --cluster) {
404         fEndLine.trim(cluster);
405     }
406     fEndLine.trim();
407 }
408 
409 SkScalar TextWrapper::getClustersTrimmedWidth() {
410     // Move the end of the line to the left
411     SkScalar width = 0;
412     bool trailingSpaces = true;
413     for (auto cluster = fClusters.endCluster(); cluster >= fClusters.startCluster(); --cluster) {
414         if (cluster->run().isPlaceholder()) {
415             continue;
416         }
417         if (trailingSpaces) {
418             if (!cluster->isWhitespaceBreak()) {
419                 width += cluster->trimmedWidth(cluster->endPos());
420                 trailingSpaces = false;
421             }
422             continue;
423         }
424         width += cluster->width();
425     }
426     return width;
427 }
428 
429 // Trim the beginning spaces in case of soft line break
430 std::tuple<Cluster*, size_t, SkScalar> TextWrapper::trimStartSpaces(Cluster* endOfClusters) {
431 
432     if (fHardLineBreak) {
433         // End of line is always end of cluster, but need to skip \n
434         auto width = fEndLine.width();
435         auto cluster = fEndLine.endCluster() + 1;
436         while (cluster < fEndLine.breakCluster() && cluster->isWhitespaceBreak())  {
437             width += cluster->width();
438             ++cluster;
439         }
440         return std::make_tuple(fEndLine.breakCluster() + 1, 0, width);
441     }
442 
443     // breakCluster points to the end of the line;
444     // It's a soft line break so we need to move lineStart forward skipping all the spaces
445     auto width = fEndLine.widthWithGhostSpaces();
446     auto cluster = fEndLine.breakCluster() + 1;
447     while (cluster < endOfClusters && cluster->isWhitespaceBreak() && !cluster->isTabulation()) {
448         width += cluster->width();
449         ++cluster;
450     }
451 
452     return std::make_tuple(cluster, 0, width);
453 }
454 
455 // calculate heuristics for different variants and select the least bad
456 
457 // calculate the total space required
458 // define the goal for line numbers (max vs space required).
459 // If text could fit, it has substantially larger score compared to nicer wrap with overflow
460 
461 // iterate: select nontrivial candidates with some maximum offset and set the penalty / benefit of variants
462 // goals: 0) fit maximum amount of text
463 //        1) fill lines
464 //        2) make line lengths even
465 //        2.5) define a cost for hyphenation - not done
466 //        3) try to make it fast
467 
468 constexpr int64_t MINIMUM_FILL_RATIO = 75;
469 constexpr int64_t MINIMUM_FILL_RATIO_SQUARED = MINIMUM_FILL_RATIO * MINIMUM_FILL_RATIO;
470 constexpr int64_t GOOD_ENOUGH_LINE_SCORE = 95 * 95;
471 constexpr int64_t UNDERFLOW_SCORE = 100;
472 constexpr float BALANCED_LAST_LINE_MULTIPLIER = 1.4f;
473 constexpr int64_t BEST_LOCAL_SCORE = -1000000;
474 constexpr float  WIDTH_TOLERANCE = 5.f;
475 constexpr int64_t PARAM_2 = 2;
476 constexpr int64_t PARAM_10000 = 10000;
477 
478 // mkay, this makes an assumption that we do the scoring runs in a single thread and holds the variables during
479 // recursion
480 struct TextWrapScorer {
481     TextWrapScorer(SkScalar maxWidth, ParagraphImpl& parent, size_t maxLines)
482         : maxWidth_(maxWidth), currentTarget_(maxWidth), maxLines_(maxLines), parent_(parent)
483     {
484         CalculateCumulativeLen(parent);
485 
486         if (parent_.getLineBreakStrategy() == LineBreakStrategy::BALANCED) {
487             // calculate target width before breaks
488             int64_t targetLines = 1 + cumulativeLen_ / maxWidth_;
489             currentTarget_ = cumulativeLen_ / targetLines;
490         }
491 
492         GenerateBreaks(parent);
493     }
494 
495     void GenerateBreaks(ParagraphImpl& parent)
496     {
497         // we trust that clusters are sorted on parent
498         bool prevWasWhitespace = false;
499         SkScalar currentWidth = 0;
500         size_t currentCount = 0; // in principle currentWidth != 0 should provide same result
501         SkScalar cumulativeLen = 0;
502         for (size_t ix = 0; ix < parent.clusters().size(); ix++) {
503             auto& cluster = parent.clusters()[ix];
504             auto len = cluster.width();
505             cumulativeLen += len;
506             currentWidth += len;
507             currentCount++;
508             if (cluster.isWhitespaceBreak()) {
509                 breaks_.emplace_back(cumulativeLen, Break::BreakType::BREAKTYPE_WHITE_SPACE, prevWasWhitespace);
510                 prevWasWhitespace = true;
511                 currentWidth = 0;
512                 currentCount = 0;
513             } else if (cluster.isHardBreak()) {
514                 breaks_.emplace_back(cumulativeLen, Break::BreakType::BREAKTYPE_HARD, false);
515                 prevWasWhitespace = true;
516                 currentWidth = 0;
517                 currentCount = 0;
518             } else if (cluster.isHyphenBreak()) {
519                 breaks_.emplace_back(cumulativeLen - cluster.width() + cluster.height(),
520                                      Break::BreakType::BREAKTYPE_HYPHEN, false);
521                 breaks_.back().reservedSpace = cluster.height();
522                 prevWasWhitespace = true;
523                 currentWidth = 0;
524                 currentCount = 0;
525             } else if (cluster.isIntraWordBreak()) {
526                 breaks_.emplace_back(cumulativeLen, Break::BreakType::BREAKTYPE_INTRA, false);
527                 prevWasWhitespace = true;
528                 currentWidth = 0;
529                 currentCount = 0;
530             } else if (currentWidth > currentTarget_) {
531                 if (currentCount > 1) {
532                     cumulativeLen -= cluster.width();
533                     ix--;
534                 }
535                 breaks_.emplace_back(cumulativeLen, Break::BreakType::BREAKTYPE_FORCED, false);
536                 prevWasWhitespace = false;
537                 currentWidth = 0;
538                 currentCount = 0;
539             } else {
540                 prevWasWhitespace = false;
541             }
542         }
543     }
544 
545     void CalculateCumulativeLen(ParagraphImpl& parent)
546     {
547         auto startCluster = &parent.cluster(0);
548         auto endCluster = &parent.cluster(0);
549         auto locale = parent.paragraphStyle().getTextStyle().getLocale();
550         for (size_t clusterIx = 0; clusterIx < parent.clusters().size(); clusterIx++) {
551             if (parent.getLineBreakStrategy() == LineBreakStrategy::BALANCED) {
552                 auto& cluster = parent.cluster(clusterIx);
553                 auto len = cluster.width();
554                 cumulativeLen_ += len;
555             }
556             CalculateHyphenPos(clusterIx, startCluster, endCluster, parent, locale);
557         }
558     }
559 
560     void CalculateHyphenPos(size_t clusterIx, Cluster*& startCluster, Cluster*& endCluster, ParagraphImpl& parent,
561                             const SkString& locale)
562     {
563         auto& cluster = parent.cluster(clusterIx);
564         const bool hyphenEnabled = parent.getWordBreakType() == WordBreakType::BREAK_HYPHEN;
565         bool isWhitespace = (cluster.isHardBreak() || cluster.isWhitespaceBreak() || cluster.isTabulation());
566         if (hyphenEnabled && !fPrevWasWhitespace && isWhitespace && endCluster > startCluster) {
567             fPrevWasWhitespace = true;
568             auto results = Hyphenator::getInstance().findBreakPositions(
569                 locale, parent.fText, startCluster->textRange().start, endCluster->textRange().end);
570             CheckHyphenBreak(results, parent, startCluster);
571             if (clusterIx + 1 < parent.clusters().size()) {
572                 startCluster = &cluster + 1;
573             }
574         } else if (!isWhitespace) {
575             fPrevWasWhitespace = false;
576             endCluster = &cluster;
577         } else { //  fix "one character + space and then the actual target"
578             uint32_t i = 1;
579             while (clusterIx + i < parent.clusters().size()) {
580                 if (!parent.cluster(clusterIx + i).isWordBreak()) {
581                     startCluster = &cluster + i;
582                     break;
583                 } else {
584                     i++;
585                 }
586             }
587         }
588     }
589 
590     void CheckHyphenBreak(std::vector<uint8_t> results, ParagraphImpl& parent, Cluster*& startCluster)
591     {
592         size_t prevClusterIx = 0;
593         for (size_t resultIx = 0; resultIx < results.size(); resultIx++) {
594             if (results[resultIx] & 0x1) {
595                 auto clusterPos = parent.clusterIndex(startCluster->textRange().start + resultIx);
596                 if (clusterPos != prevClusterIx) {
597                     parent.cluster(clusterPos).enableHyphenBreak();
598                     prevClusterIx = clusterPos;
599                 }
600             }
601         }
602     }
603 
604     struct RecursiveParam {
605         int64_t targetLines;
606         size_t maxLines;
607         size_t lineNumber;
608         SkScalar begin;
609         SkScalar remainingTextWidth;
610         SkScalar currentMax;
611         size_t breakPos;
612     };
613 
614     void Run() {
615         int64_t targetLines = 1 + cumulativeLen_ / maxWidth_;
616 
617         if (parent_.getLineBreakStrategy() == LineBreakStrategy::BALANCED) {
618             currentTarget_ = cumulativeLen_ / targetLines;
619         }
620 
621         if (targetLines < PARAM_2) {
622             // need to have at least two lines for algo to do anything useful
623             return;
624         }
625         CalculateRecursive(RecursiveParam{
626             targetLines, maxLines_, 0, 0.f, cumulativeLen_,
627         });
628         LOGD("cache_: %{public}zu", cache_.size());
629     }
630 
631     int64_t CalculateRecursive(RecursiveParam param)
632     {
633         if (param.maxLines == 0 || param.remainingTextWidth <= 1.f) {
634             return BEST_LOCAL_SCORE;
635         }
636 
637         // This should come precalculated
638         param.currentMax = maxWidth_ - parent_.detectIndents(param.lineNumber);
639         if (nearlyZero(param.currentMax)) {
640             return BEST_LOCAL_SCORE;
641         }
642 
643         // trim possible spaces at the beginning of line
644         while ((param.lineNumber > 0) && (lastBreakPos_ + 1 < breaks_.size()) &&
645             (breaks_[lastBreakPos_ + 1].subsequentWhitespace)) {
646             param.remainingTextWidth += (param.begin - breaks_[++lastBreakPos_].width);
647             param.begin = breaks_[lastBreakPos_].width;
648         }
649 
650         if (lastBreakPos_ < breaks_.size() && breaks_[lastBreakPos_].type == Break::BreakType::BREAKTYPE_FORCED) {
651             lastBreakPos_++;
652         }
653         param.breakPos = lastBreakPos_;
654 
655         while (param.breakPos < breaks_.size() && breaks_[param.breakPos].width < (param.begin + param.currentMax)) {
656             param.breakPos++;
657         }
658 
659         if (param.breakPos == lastBreakPos_ && param.remainingTextWidth > param.currentMax) {
660             // If we were unable to find a break that matches the criteria, insert new one
661             // This may happen if there is a long word and per line indent for this particular line
662             if (param.breakPos + 1 > breaks_.size()) {
663                 breaks_.emplace_back(param.begin + param.currentMax, Break::BreakType::BREAKTYPE_FORCED, false);
664             } else {
665                 breaks_.insert(breaks_.cbegin() + param.breakPos + 1, Break(param.begin + param.currentMax,
666                     Break::BreakType::BREAKTYPE_FORCED, false));
667             }
668             param.breakPos += BREAK_NUM_TWO;
669         }
670 
671         LOGD("Line %{public}lu about to loop %{public}f, %{public}lu, %{public}lu, max: %{public}f",
672             static_cast<unsigned long>(param.lineNumber), param.begin, static_cast<unsigned long>(param.breakPos),
673             static_cast<unsigned long>(lastBreakPos_), maxWidth_);
674 
675         return FindOptimalSolutionForCurrentLine(param);
676     }
677 
678     std::vector<SkScalar>& GetResult()
679     {
680         return current_;
681     }
682 
683     SkScalar calculateCurrentWidth(RecursiveParam& param, bool looped)
684     {
685         SkScalar newWidth = param.currentMax;
686 
687         if (param.breakPos > 0 && param.begin < breaks_[param.breakPos - 1].width) {
688             newWidth = std::min(breaks_[--param.breakPos].width - param.begin, param.currentMax);
689         }
690 
691         if (looped
692             && ((lastBreakPos_ == param.breakPos)
693                 || (newWidth / param.currentMax * UNDERFLOW_SCORE < MINIMUM_FILL_RATIO))) {
694             LOGD("line %{public}lu breaking %{public}f, %{public}lu, %{public}f/%{public}f",
695                  static_cast<unsigned long>(param.lineNumber), param.begin, static_cast<unsigned long>(param.breakPos),
696                  newWidth, maxWidth_);
697             return 0;
698         }
699 
700         lastBreakPos_ = param.breakPos;
701 
702         return std::min(newWidth, param.remainingTextWidth);
703     }
704 
705     int64_t FindOptimalSolutionForCurrentLine(RecursiveParam& param)
706     {
707         // have this in reversed order to avoid extra insertions
708         std::vector<SkScalar> currentBest;
709         bool looped = false;
710         int64_t score = 0;
711         int64_t overallScore = score;
712         int64_t bestLocalScore = BEST_LOCAL_SCORE;
713         do {
714             // until the given threshold is crossed (minimum line fill rate)
715             // re-break this line, if a result is different, calculate score
716             SkScalar currentWidth = calculateCurrentWidth(param, looped);
717             if (currentWidth == 0) {
718                 break;
719             }
720             Index index { param.lineNumber, param.begin, currentWidth };
721 
722             // check cache
723             const auto& ite = cache_.find(index);
724             if (ite != cache_.cend()) {
725                 cacheHits_++;
726                 current_ = ite->second.widths;
727                 overallScore = ite->second.score;
728                 UpdateSolution(bestLocalScore, overallScore, currentBest);
729                 looped = true;
730                 continue;
731             }
732             SkScalar scoref = std::min(1.f, abs(currentTarget_ - currentWidth) / currentTarget_);
733             score = int64_t((1.f - scoref) * UNDERFLOW_SCORE);
734             score *= score;
735 
736             current_.clear();
737             overallScore = score;
738 
739             // Handle last line
740             if (breaks_[param.breakPos].type == Break::BreakType::BREAKTYPE_HYPHEN) {
741                 auto copy = currentWidth - breaks_[param.breakPos].reservedSpace;
742                 // name is bit confusing as the method enters also recursion
743                 // with hyphen break this never is the last line
744                 if (!HandleLastLine(param, overallScore, copy, score)) {
745                     break;
746                 }
747             } else { // real last line may update currentWidth
748                 if (!HandleLastLine(param, overallScore, currentWidth, score)) {
749                     break;
750                 }
751             }
752             // we have exceeded target number of lines, add some penalty
753             if (param.targetLines < 0) {
754                 overallScore += param.targetLines * PARAM_10000; // MINIMUM_FILL_RATIO;
755             }
756 
757             // We always hold the best possible score of children at this point
758             current_.push_back(currentWidth);
759             cache_[index] = { overallScore, current_ };
760 
761             UpdateSolution(bestLocalScore, overallScore, currentBest);
762             looped = true;
763         } while (score > MINIMUM_FILL_RATIO_SQUARED &&
764             !(param.lineNumber == 0 && bestLocalScore > param.targetLines * GOOD_ENOUGH_LINE_SCORE));
765         current_ = currentBest;
766         return bestLocalScore;
767     }
768 
769     bool HandleLastLine(RecursiveParam& param, int64_t& overallScore, SkScalar& currentWidth, int64_t&score)
770     {
771         // Handle last line
772         if (abs(currentWidth - param.remainingTextWidth) < 1.f) {
773             // this is last line, with high-quality wrapping, relax the score a bit
774             if (parent_.getLineBreakStrategy() == LineBreakStrategy::HIGH_QUALITY) {
775                 overallScore = std::max(MINIMUM_FILL_RATIO, overallScore);
776             } else {
777                 overallScore *= BALANCED_LAST_LINE_MULTIPLIER;
778             }
779 
780             // let's break the loop, under no same condition / fill-rate added rows can result to a better
781             // score.
782             currentWidth = param.currentMax;
783             score = MINIMUM_FILL_RATIO_SQUARED - 1;
784             LOGD("last line %{public}lu reached", static_cast<unsigned long>(param.lineNumber));
785             return true;
786         }
787         if (((param.remainingTextWidth - currentWidth) / maxWidth_) < param.maxLines) {
788             // recursively calculate best score for children
789             overallScore += CalculateRecursive(RecursiveParam{
790                 param.targetLines - 1,
791                 param.maxLines > param.lineNumber ? param.maxLines - param.lineNumber : 0,
792                 param.lineNumber + 1,
793                 param.begin + currentWidth,
794                 param.remainingTextWidth - currentWidth
795             });
796             lastBreakPos_ = param.breakPos; // restore our ix
797             return true;
798         }
799 
800         // the text is not going to fit anyway (anymore), no need to push it
801         return false;
802     }
803 
804     void UpdateSolution(int64_t& bestLocalScore, const int64_t overallScore, std::vector<SkScalar>& currentBest)
805     {
806         if (overallScore > bestLocalScore) {
807             bestLocalScore = overallScore;
808             currentBest = current_;
809         }
810     }
811 
812 private:
813     struct Index {
814         size_t lineNumber { 0 };
815         SkScalar begin { 0 };
816         SkScalar width { 0 };
817         bool operator==(const Index& other) const
818         {
819             return (lineNumber == other.lineNumber && fabs(begin - other.begin) < WIDTH_TOLERANCE &&
820                 fabs(width - other.width) < WIDTH_TOLERANCE);
821         }
822         bool operator<(const Index& other) const
823         {
824             return lineNumber < other.lineNumber ||
825                 (lineNumber == other.lineNumber && other.begin - begin > WIDTH_TOLERANCE) ||
826                 (lineNumber == other.lineNumber && fabs(begin - other.begin) < WIDTH_TOLERANCE &&
827                 other.width - width > WIDTH_TOLERANCE);
828         }
829     };
830 
831     struct Score {
832         int64_t score { 0 };
833         // in reversed order
834         std::vector<SkScalar> widths;
835     };
836 
837     // to be seen if unordered map would be better fit
838     std::map<Index, Score> cache_;
839 
840     SkScalar maxWidth_ { 0 };
841     SkScalar currentTarget_ { 0 };
842     SkScalar cumulativeLen_ { 0 };
843     size_t maxLines_ { 0 };
844     ParagraphImpl& parent_;
845     std::vector<SkScalar> current_;
846 
847     struct Break {
848         enum class BreakType {
849             BREAKTYPE_NONE,
850             BREAKTYPE_HARD,
851             BREAKTYPE_WHITE_SPACE,
852             BREAKTYPE_INTRA,
853             BREAKTYPE_FORCED,
854             BREAKTYPE_HYPHEN
855         };
856         Break(SkScalar w, BreakType t, bool ssws) : width(w), type(t), subsequentWhitespace(ssws) {}
857 
858         SkScalar width { 0.f };
859         BreakType type { BreakType::BREAKTYPE_NONE };
860         bool subsequentWhitespace { false };
861         SkScalar reservedSpace { 0.f };
862     };
863 
864     std::vector<Break> breaks_;
865     size_t lastBreakPos_ { 0 };
866 
867     uint64_t cacheHits_ { 0 };
868 	bool fPrevWasWhitespace{false};
869 };
870 
871 uint64_t TextWrapper::CalculateBestScore(std::vector<SkScalar>& widthOut, SkScalar maxWidth,
872     ParagraphImpl* parent, size_t maxLines) {
873     if (maxLines == 0 || !parent || nearlyZero(maxWidth)) {
874         return -1;
875     }
876 
877     TextWrapScorer* scorer = new TextWrapScorer(maxWidth, *parent, maxLines);
878     scorer->Run();
879     while (scorer && scorer->GetResult().size()) {
880         auto width = scorer->GetResult().back();
881         widthOut.push_back(width);
882         LOGD("width %{public}f", width);
883         scorer->GetResult().pop_back();
884     }
885 
886     delete scorer;
887     return 0;
888 }
889 
890 void TextWrapper::updateMetricsWithPlaceholder(std::vector<Run*>& runs, bool iterateByCluster) {
891     if (!iterateByCluster) {
892         Run* lastRun = nullptr;
893         for (auto& run : runs) {
894             if (run == lastRun) {
895                 continue;
896             }
897             lastRun = run;
898             if (lastRun != nullptr && lastRun->placeholderStyle() != nullptr) {
899                 SkASSERT(lastRun->size() == 1);
900                 // Update the placeholder metrics so we can get the placeholder positions later
901                 // and the line metrics (to make sure the placeholder fits)
902                 lastRun->updateMetrics(&fEndLine.metrics());
903             }
904         }
905         return;
906     }
907     runs.clear();
908     // Deal with placeholder clusters == runs[@size==1]
909     Run* lastRun = nullptr;
910     for (auto cluster = fEndLine.startCluster(); cluster <= fEndLine.endCluster(); ++cluster) {
911         auto r = cluster->runOrNull();
912         if (r == lastRun) {
913             continue;
914         }
915         lastRun = r;
916         if (lastRun != nullptr && lastRun->placeholderStyle() != nullptr) {
917             SkASSERT(lastRun->size() == 1);
918             // Update the placeholder metrics so we can get the placeholder positions later
919             // and the line metrics (to make sure the placeholder fits)
920             lastRun->updateMetrics(&fEndLine.metrics());
921             runs.emplace_back(lastRun);
922         }
923     }
924 }
925 
926 // TODO: refactor the code for line ending (with/without ellipsis)
927 void TextWrapper::breakTextIntoLines(ParagraphImpl* parent,
928                                      SkScalar maxWidth,
929                                      const AddLineToParagraph& addLine) {
930     initParent(parent);
931 
932     if (fParent->getLineBreakStrategy() == LineBreakStrategy::BALANCED &&
933         fParent->getWordBreakType() != WordBreakType::BREAK_ALL &&
934         fParent->getWordBreakType() != WordBreakType::BREAK_HYPHEN) {
935         layoutLinesBalanced(parent, maxWidth, addLine);
936         return;
937     }
938 
939     layoutLinesSimple(parent, maxWidth, addLine);
940 }
941 
942 void TextWrapper::layoutLinesSimple(ParagraphImpl* parent,
943                                      SkScalar maxWidth,
944                                      const AddLineToParagraph& addLine) {
945     auto span = parent->clusters();
946     if (span.empty()) {
947         return;
948     }
949     auto maxLines = parent->paragraphStyle().getMaxLines();
950     auto align = parent->paragraphStyle().effective_align();
951     auto unlimitedLines = maxLines == std::numeric_limits<size_t>::max();
952     auto endlessLine = !SkIsFinite(maxWidth);
953     auto hasEllipsis = parent->paragraphStyle().ellipsized();
954 
955     auto disableFirstAscent = parent->paragraphStyle().getTextHeightBehavior() & TextHeightBehavior::kDisableFirstAscent;
956     auto disableLastDescent = parent->paragraphStyle().getTextHeightBehavior() & TextHeightBehavior::kDisableLastDescent;
957     bool firstLine = true; // We only interested in fist line if we have to disable the first ascent
958 
959     // Resolve balanced line widths
960     std::vector<SkScalar> balancedWidths;
961 
962     // if word breaking strategy is nontrivial (balanced / optimal), AND word break mode is not BREAK_ALL
963     if (parent->getWordBreakType() != WordBreakType::BREAK_ALL &&
964         parent->getLineBreakStrategy() != LineBreakStrategy::GREEDY) {
965         if (CalculateBestScore(balancedWidths, maxWidth, parent, maxLines) < 0) {
966             // if the line breaking strategy returns a negative score, the algorithm could not fit or break the text
967             // fall back to default, greedy algorithm
968             balancedWidths.clear();
969         }
970         LOGD("Got %{public}lu", static_cast<unsigned long>(balancedWidths.size()));
971     }
972 
973     SkScalar softLineMaxIntrinsicWidth = 0;
974     fEndLine = TextStretch(span.begin(), span.begin(), parent->strutForceHeight() && parent->strutEnabled());
975     auto end = span.end() - 1;
976     auto start = span.begin();
977     InternalLineMetrics maxRunMetrics;
978     bool needEllipsis = false;
979     SkScalar newWidth = maxWidth;
980     SkScalar noIndentWidth = maxWidth;
981     while (fEndLine.endCluster() != end) {
982         noIndentWidth = maxWidth - parent->detectIndents(fLineNumber - 1);
983         if (maxLines == 1 &&
984             (parent->paragraphStyle().getEllipsisMod() == EllipsisModal::HEAD ||
985             parent->needCreateMiddleEllipsis())) {
986             newWidth = FLT_MAX;
987         } else if (!balancedWidths.empty() && fLineNumber - 1 < balancedWidths.size()) {
988             newWidth = balancedWidths[fLineNumber - 1];
989         } else {
990             newWidth = maxWidth - parent->detectIndents(fLineNumber - 1);
991         }
992         auto lastLine = (hasEllipsis && unlimitedLines) || fLineNumber >= maxLines;
993         needEllipsis = hasEllipsis && !endlessLine && lastLine;
994 
995         this->lookAhead(newWidth, end, parent->getApplyRoundingHack(), parent->getWordBreakType(), needEllipsis);
996 
997         this->moveForward(needEllipsis, parent->getWordBreakType() == WordBreakType::BREAK_ALL);
998         if (fEndLine.endCluster() >= fEndLine.startCluster() || maxLines > 1) {
999             needEllipsis &= fEndLine.endCluster() < end - 1; // Only if we have some text to ellipsize
1000         }
1001 
1002         // Do not trim end spaces on the naturally last line of the left aligned text
1003         this->trimEndSpaces(align);
1004 
1005         // For soft line breaks add to the line all the spaces next to it
1006         Cluster* startLine;
1007         size_t pos;
1008         SkScalar widthWithSpaces;
1009         std::tie(startLine, pos, widthWithSpaces) = this->trimStartSpaces(end);
1010 
1011         if (needEllipsis && !fHardLineBreak) {
1012             // This is what we need to do to preserve a space before the ellipsis
1013             fEndLine.restoreBreak();
1014             widthWithSpaces = fEndLine.widthWithGhostSpaces();
1015         } else if (fBrokeLineWithHyphen) {
1016             fEndLine.shiftWidth(fEndLine.endCluster()->width());
1017         }
1018 
1019         // If the line is empty with the hard line break, let's take the paragraph font (flutter???)
1020         if (fEndLine.metrics().isClean()) {
1021             fEndLine.setMetrics(parent->getEmptyMetrics());
1022         }
1023 
1024         std::vector<Run*> runs;
1025         updateMetricsWithPlaceholder(runs, true);
1026         // update again for some case
1027         // such as :
1028         //      placeholderA(width: 100, height: 100, align: bottom) placeholderB(width: 200, height: 200, align: top)
1029         // without second update: the placeholderA bottom will be set 0, and placeholderB bottom will be set 100
1030         // so the fEndline bottom will be set 100, is not equal placeholderA bottom
1031         updateMetricsWithPlaceholder(runs, false);
1032         // Before we update the line metrics with struts,
1033         // let's save it for GetRectsForRange(RectHeightStyle::kMax)
1034         maxRunMetrics = fEndLine.metrics();
1035         maxRunMetrics.fForceStrut = false;
1036 
1037         // TODO: keep start/end/break info for text and runs but in a better way that below
1038         TextRange textExcludingSpaces(fEndLine.startCluster()->textRange().start, fEndLine.endCluster()->textRange().end);
1039         TextRange text(fEndLine.startCluster()->textRange().start, fEndLine.breakCluster()->textRange().start);
1040         TextRange textIncludingNewlines(fEndLine.startCluster()->textRange().start, startLine->textRange().start);
1041         if (startLine == end) {
1042             textIncludingNewlines.end = parent->text().size();
1043             text.end = parent->text().size();
1044         }
1045         ClusterRange clusters(fEndLine.startCluster() - start, fEndLine.endCluster() - start + 1);
1046         ClusterRange clustersWithGhosts(fEndLine.startCluster() - start, startLine - start);
1047 
1048         if (disableFirstAscent && firstLine) {
1049             fEndLine.metrics().fAscent = fEndLine.metrics().fRawAscent;
1050         }
1051         if (disableLastDescent && (lastLine || (startLine == end && !fHardLineBreak))) {
1052             fEndLine.metrics().fDescent = fEndLine.metrics().fRawDescent;
1053         }
1054 
1055         if (parent->strutEnabled()) {
1056             // Make sure font metrics are not less than the strut
1057             parent->strutMetrics().updateLineMetrics(fEndLine.metrics());
1058         }
1059 
1060         SkScalar lineHeight = fEndLine.metrics().height();
1061         if (fHardLineBreak && !lastLine && parent->paragraphStyle().getParagraphSpacing() > 0) {
1062             lineHeight += parent->paragraphStyle().getParagraphSpacing();
1063         }
1064         firstLine = false;
1065 
1066         if (fEndLine.empty()) {
1067             // Correct text and clusters (make it empty for an empty line)
1068             textExcludingSpaces.end = textExcludingSpaces.start;
1069             clusters.end = clusters.start;
1070         }
1071 
1072         // In case of a force wrapping we don't have a break cluster and have to use the end cluster
1073         text.end = std::max(text.end, textExcludingSpaces.end);
1074 
1075         if (parent->paragraphStyle().getEllipsisMod() == EllipsisModal::HEAD && hasEllipsis) {
1076             needEllipsis = maxLines <= 1;
1077             if (needEllipsis) {
1078                 fHardLineBreak = false;
1079             }
1080         }
1081 
1082         SkScalar offsetX = parent->detectIndents(fLineNumber - 1);
1083         addLine(textExcludingSpaces,
1084                 text,
1085                 textIncludingNewlines, clusters, clustersWithGhosts, widthWithSpaces,
1086                 fEndLine.startPos(),
1087                 fEndLine.endPos(),
1088                 SkVector::Make(offsetX, fHeight),
1089                 SkVector::Make(fEndLine.width(), lineHeight),
1090                 fEndLine.metrics(),
1091                 needEllipsis,
1092                 offsetX,
1093                 noIndentWidth);
1094 
1095         softLineMaxIntrinsicWidth += widthWithSpaces;
1096 
1097         fMaxIntrinsicWidth = std::max(fMaxIntrinsicWidth, softLineMaxIntrinsicWidth);
1098         if (fHardLineBreak) {
1099             softLineMaxIntrinsicWidth = 0;
1100         }
1101         // Start a new line
1102         fHeight += lineHeight;
1103         if (!fHardLineBreak || startLine != end) {
1104             fEndLine.clean();
1105         }
1106         fEndLine.startFrom(startLine, pos);
1107         parent->fMaxWidthWithTrailingSpaces = std::max(parent->fMaxWidthWithTrailingSpaces, widthWithSpaces);
1108 
1109         if (hasEllipsis && unlimitedLines) {
1110             // There is one case when we need an ellipsis on a separate line
1111             // after a line break when width is infinite
1112             if (!fHardLineBreak) {
1113                 break;
1114             }
1115         } else if (lastLine) {
1116             // There is nothing more to draw
1117             fHardLineBreak = false;
1118             break;
1119         }
1120 
1121         ++fLineNumber;
1122     }
1123     if (parent->paragraphStyle().getIsEndAddParagraphSpacing() &&
1124         parent->paragraphStyle().getParagraphSpacing() > 0) {
1125         fHeight += parent->paragraphStyle().getParagraphSpacing();
1126     }
1127 
1128     // We finished formatting the text but we need to scan the rest for some numbers
1129     // TODO: make it a case of a normal flow
1130     if (fEndLine.endCluster() != nullptr) {
1131         auto lastWordLength = 0.0f;
1132         auto cluster = fEndLine.endCluster();
1133         while (cluster != end || cluster->endPos() < end->endPos()) {
1134             fExceededMaxLines = true;
1135             if (cluster->isHardBreak()) {
1136                 // Hard line break ends the word and the line
1137                 fMaxIntrinsicWidth = std::max(fMaxIntrinsicWidth, softLineMaxIntrinsicWidth);
1138                 softLineMaxIntrinsicWidth = 0;
1139                 fMinIntrinsicWidth = std::max(fMinIntrinsicWidth, lastWordLength);
1140                 lastWordLength = 0;
1141             } else if (cluster->isWhitespaceBreak()) {
1142                 // Whitespaces end the word
1143                 softLineMaxIntrinsicWidth += cluster->width();
1144                 fMinIntrinsicWidth = std::max(fMinIntrinsicWidth, lastWordLength);
1145                 lastWordLength = 0;
1146             } else if (cluster->run().isPlaceholder()) {
1147                 // Placeholder ends the previous word and creates a separate one
1148                 fMinIntrinsicWidth = std::max(fMinIntrinsicWidth, lastWordLength);
1149                 // Placeholder width now counts in fMinIntrinsicWidth
1150                 softLineMaxIntrinsicWidth += cluster->width();
1151                 fMinIntrinsicWidth = std::max(fMinIntrinsicWidth, cluster->width());
1152                 lastWordLength = 0;
1153             } else {
1154                 // Nothing out of ordinary - just add this cluster to the word and to the line
1155                 softLineMaxIntrinsicWidth += cluster->width();
1156                 lastWordLength += cluster->width();
1157             }
1158             ++cluster;
1159         }
1160         fMinIntrinsicWidth = std::max(fMinIntrinsicWidth, lastWordLength);
1161         fMaxIntrinsicWidth = std::max(fMaxIntrinsicWidth, softLineMaxIntrinsicWidth);
1162 
1163         if (parent->lines().empty()) {
1164             // In case we could not place even a single cluster on the line
1165             if (disableFirstAscent) {
1166                 fEndLine.metrics().fAscent = fEndLine.metrics().fRawAscent;
1167             }
1168             if (disableLastDescent && !fHardLineBreak) {
1169                 fEndLine.metrics().fDescent = fEndLine.metrics().fRawDescent;
1170             }
1171             fHeight = std::max(fHeight, fEndLine.metrics().height());
1172         }
1173     }
1174 
1175     if (fHardLineBreak) {
1176         if (disableLastDescent) {
1177             fEndLine.metrics().fDescent = fEndLine.metrics().fRawDescent;
1178         }
1179 
1180         // Last character is a line break
1181         if (parent->strutEnabled()) {
1182             // Make sure font metrics are not less than the strut
1183             parent->strutMetrics().updateLineMetrics(fEndLine.metrics());
1184         }
1185 
1186         ClusterRange clusters(fEndLine.breakCluster() - start, fEndLine.endCluster() - start);
1187         addLine(fEndLine.breakCluster()->textRange(),
1188                 fEndLine.breakCluster()->textRange(),
1189                 fEndLine.endCluster()->textRange(),
1190                 clusters,
1191                 clusters,
1192                 0,
1193                 0,
1194                 0,
1195                 SkVector::Make(0, fHeight),
1196                 SkVector::Make(0, fEndLine.metrics().height()),
1197                 fEndLine.metrics(),
1198                 needEllipsis,
1199                 parent->detectIndents(fLineNumber - 1),
1200                 noIndentWidth);
1201         fHeight += fEndLine.metrics().height();
1202         parent->lines().back().setMaxRunMetrics(maxRunMetrics);
1203     }
1204 
1205     if (parent->lines().empty()) {
1206         return;
1207     }
1208     // Correct line metric styles for the first and for the last lines if needed
1209     if (disableFirstAscent) {
1210         parent->lines().front().setAscentStyle(LineMetricStyle::Typographic);
1211     }
1212     if (disableLastDescent) {
1213         parent->lines().back().setDescentStyle(LineMetricStyle::Typographic);
1214     }
1215 }
1216 
1217 std::vector<uint8_t> TextWrapper::findBreakPositions(Cluster* startCluster,
1218                                                      Cluster* endOfClusters,
1219                                                      SkScalar widthBeforeCluster,
1220                                                      SkScalar maxWidth) {
1221     auto startPos = startCluster->textRange().start;
1222     auto endPos = startPos;
1223     auto owner = startCluster->getOwner();
1224     for (auto next = startCluster + 1; next != endOfClusters; next++) {
1225         // find the end boundary of current word (hard/soft/whitespace break)
1226         if (next->isWhitespaceBreak() || next->isHardBreak()) {
1227             break;
1228         } else {
1229             endPos = next->textRange().end;
1230         }
1231     }
1232 
1233     // Using end position cluster height as an estimate for reserved hyphen width
1234     // hyphen may not be shaped at this point. End pos may be non-drawable, so prefer
1235     // previous glyph before break
1236     auto mappedEnd = owner->fClustersIndexFromCodeUnit[endPos];
1237     auto len = widthBeforeCluster + owner->cluster(mappedEnd > 0 ? mappedEnd - 1 : 0).height();
1238     // ToDo: We can stop here based on hyhpenation frequency setting
1239     // but there is no need to proceed with hyphenation if we don't have space for even hyphen
1240     if (std::isnan(len) || len >= maxWidth) {
1241         return std::vector<uint8_t>{};
1242     }
1243 
1244     auto locale = owner->paragraphStyle().getTextStyle().getLocale();
1245     return Hyphenator::getInstance().findBreakPositions(locale, owner->fText, startPos, endPos);
1246 }
1247 
1248 void TextWrapper::pushToWordStretches() {
1249     fWordStretches.push_back(fClusters);
1250     fWordWidthGroups.push_back(fClusters.width());
1251     fClusters.clean();
1252 }
1253 
1254 void TextWrapper::pushToWordStretchesBatch() {
1255     fWordStretchesBatch.push_back(fWordStretches);
1256     fWordWidthGroupsBatch.push_back(fWordWidthGroups);
1257     fWordStretches.clear();
1258     fWordWidthGroups.clear();
1259 }
1260 
1261 void TextWrapper::generateLineStretches(const std::vector<std::pair<size_t, size_t>>& linesGroupInfo,
1262     std::vector<TextStretch>& wordStretches) {
1263     for (const std::pair<size_t, size_t>& pair : linesGroupInfo) {
1264         TextStretch endLine{};
1265         for (size_t i = pair.first; i <= pair.second; ++i) {
1266             if (i == pair.first) {
1267                 endLine.setStartCluster(wordStretches[i].startCluster());
1268             }
1269             endLine.extend(wordStretches[i]);
1270         }
1271         fLineStretches.push_back(endLine);
1272     }
1273 }
1274 
1275 void TextWrapper::extendCommonCluster(Cluster* cluster, TextTabAlign& textTabAlign,
1276     SkScalar& totalFakeSpacing, WordBreakType wordBreakType) {
1277     if (cluster->isTabulation()) {
1278         if (textTabAlign.processTab(fWords, fClusters, cluster, totalFakeSpacing)) {
1279             return;
1280         }
1281         fClusters.extend(cluster);
1282 
1283         fMinIntrinsicWidth = std::max(fMinIntrinsicWidth, getClustersTrimmedWidth());
1284         pushToWordStretches();
1285     } else {
1286         fClusters.extend(cluster);
1287         if (fClusters.endOfWord()) { // Keep adding clusters/words
1288             textTabAlign.processEndofWord(fWords, fClusters, cluster, totalFakeSpacing);
1289             fMinIntrinsicWidth = std::max(fMinIntrinsicWidth, getClustersTrimmedWidth());
1290             pushToWordStretches();
1291         } else {
1292             if (textTabAlign.processCluster(fWords, fClusters, cluster, totalFakeSpacing)) {
1293                 fClusters.trim(cluster);
1294             }
1295         }
1296     }
1297 }
1298 
1299 void TextWrapper::generateWordStretches(const SkSpan<Cluster>& span, WordBreakType wordBreakType) {
1300     fEndLine.metrics().clean();
1301 
1302     if (fStart == nullptr) {
1303         fStart = span.begin();
1304         fEnd = span.end() - 1;
1305     }
1306 
1307     fClusters.startFrom(fStart, fStart->startPos());
1308     fClip.startFrom(fStart, fStart->startPos());
1309 
1310     TextTabAlign textTabAlign(fStart->getOwner()->paragraphStyle().getTextTab());
1311     textTabAlign.init(MAX_LINES_LIMIT, fStart);
1312 
1313     SkScalar totalFakeSpacing = 0.0;
1314 
1315     for (Cluster* cluster = fStart; cluster < fEnd; ++cluster) {
1316         totalFakeSpacing += (cluster->needAutoSpacing() && cluster != fStart)
1317                                     ? (cluster - 1)->getFontSize() / AUTO_SPACING_WIDTH_RATIO
1318                                     : 0;
1319         if (cluster->run().isPlaceholder()) {
1320             if (!fClusters.empty()) {
1321                 // Placeholder ends the previous word (placeholders are ignored in trimming)
1322                 fMinIntrinsicWidth = std::max(fMinIntrinsicWidth, getClustersTrimmedWidth());
1323                 pushToWordStretches();
1324             }
1325 
1326             // Placeholder is separate word and its width now is counted in minIntrinsicWidth
1327             fMinIntrinsicWidth = std::max(fMinIntrinsicWidth, cluster->width());
1328             fClusters.extend(cluster);
1329             pushToWordStretches();
1330         } else {
1331             extendCommonCluster(cluster, textTabAlign, totalFakeSpacing, wordBreakType);
1332         }
1333 
1334         if (cluster->isHardBreak()) {
1335             fStart = cluster;
1336             pushToWordStretchesBatch();
1337         }
1338     }
1339 
1340     if (!fEnd->isHardBreak()) {
1341         pushToWordStretchesBatch();
1342     }
1343 }
1344 
1345 SkScalar TextWrapper::getTextStretchTrimmedEndSpaceWidth(const TextStretch& stretch) {
1346     SkScalar width = stretch.width();
1347     for (Cluster* cluster = stretch.endCluster(); cluster >= stretch.startCluster(); --cluster) {
1348         if (nearlyEqual(cluster->width(), 0) && cluster->run().isPlaceholder()) {
1349             continue;
1350         }
1351 
1352         if (!cluster->isWhitespaceBreak()) {
1353             break;
1354         }
1355         width -= cluster->width();
1356     }
1357     return width;
1358 }
1359 
1360 void calculateCostTable(const std::vector<SkScalar>& clustersWidth,
1361                         SkScalar maxWidth,
1362                         std::vector<SkScalar>& costTable,
1363                         std::vector<std::pair<size_t, size_t>>& bestPick) {
1364     int clustersCnt = static_cast<int>(clustersWidth.size());
1365     for (int clustersIndex = clustersCnt - STRATEGY_START_POS; clustersIndex >= 0; --clustersIndex) {
1366         bestPick[clustersIndex].first = static_cast<size_t>(clustersIndex);
1367         SkScalar rowCurrentLen = 0;
1368         std::vector<SkScalar> costList;
1369 
1370         int maxWord = clustersIndex;
1371         for (int j = clustersIndex; j < clustersCnt; j++) {
1372             rowCurrentLen += clustersWidth[j];
1373             if (rowCurrentLen > maxWidth) {
1374                 rowCurrentLen -= clustersWidth[j];
1375                 maxWord = j - 1;
1376                 break;
1377             }
1378             maxWord = j;
1379         }
1380         if (maxWord < clustersIndex) {
1381             maxWord = clustersIndex;
1382         }
1383 
1384         for (int j = clustersIndex; j <= maxWord; ++j) {
1385             SkScalar cost = std::pow(std::abs(std::accumulate(clustersWidth.begin() + clustersIndex,
1386                                                               clustersWidth.begin() + j + 1,
1387                                                               0) -
1388                                               maxWidth),
1389                                      STRATEGY_START_POS);
1390             if (j + 1 <= clustersCnt - 1) {
1391                 cost += costTable[j + 1];
1392             }
1393             costList.push_back(cost);
1394         }
1395 
1396         SkScalar minCost = *std::min_element(costList.begin(), costList.end());
1397         std::vector<size_t> minCostIndices;
1398         for (size_t q = 0; q < costList.size(); ++q) {
1399             if (nearlyZero(costList[q], minCost)) {
1400                 minCostIndices.push_back(q);
1401             }
1402         }
1403         size_t minCostIdx{costList.size() - 1};
1404         if (minCostIndices.size() > 0) {
1405             minCostIdx = minCostIndices[static_cast<int32_t>(minCostIndices.size() / MIN_COST_POS)];
1406         }
1407         costTable[clustersIndex] = minCost;
1408         bestPick[clustersIndex].second = static_cast<size_t>(clustersIndex + minCostIdx);
1409     }
1410 }
1411 
1412 std::vector<std::pair<size_t, size_t>> buildWordBalance(
1413         const std::vector<std::pair<size_t, size_t>>& bestPick, int clustersCnt) {
1414     int rowStart{0};
1415     std::vector<std::pair<size_t, size_t>> wordBalance;
1416 
1417     while (rowStart < clustersCnt) {
1418         int rowEnd = bestPick[rowStart].second;
1419         wordBalance.emplace_back(rowStart, rowEnd);
1420         rowStart = rowEnd + 1;
1421     }
1422     return wordBalance;
1423 }
1424 
1425 std::vector<std::pair<size_t, size_t>> TextWrapper::generateLinesGroupInfo(
1426         const std::vector<SkScalar>& clustersWidth, SkScalar maxWidth) {
1427     std::vector<std::pair<size_t, size_t>> wordBalance;
1428     if (clustersWidth.empty()) {
1429         return wordBalance;
1430     }
1431     int clustersCnt = static_cast<int>(clustersWidth.size());
1432     std::vector<SkScalar> costTable(clustersCnt, 0);
1433     std::vector<std::pair<size_t, size_t>> bestPick(clustersCnt, {0, 0});
1434 
1435     costTable[clustersCnt - 1] =
1436             std::pow(std::abs(clustersWidth[clustersCnt - 1] - maxWidth), STRATEGY_START_POS);
1437     bestPick[clustersCnt - 1] = {clustersCnt - 1, clustersCnt - 1};
1438 
1439     calculateCostTable(clustersWidth, maxWidth, costTable, bestPick);
1440 
1441     return buildWordBalance(bestPick, clustersCnt);
1442 }
1443 
1444 std::vector<SkScalar> TextWrapper::generateWordsWidthInfo(const std::vector<TextStretch>& wordStretches) {
1445     std::vector<SkScalar> result;
1446     result.reserve(wordStretches.size());
1447     std::transform(wordStretches.begin(),
1448                    wordStretches.end(),
1449                    std::back_inserter(result),
1450                    [](const TextStretch& word) { return word.width(); });
1451     return result;
1452 }
1453 
1454 void TextWrapper::formalizedClusters(std::vector<TextStretch>& clusters, SkScalar limitWidth) {
1455     for (auto it = clusters.begin(); it != clusters.end(); it++) {
1456         if (it->width() < limitWidth) {
1457             continue;
1458         }
1459         std::list<TextStretch> trimmedWordList{*it};
1460 
1461         for (auto trimmedItor = trimmedWordList.begin(); trimmedItor != trimmedWordList.end();) {
1462             if (trimmedItor->width() < limitWidth) {
1463                 ++trimmedItor;
1464                 continue;
1465             }
1466             auto result = trimmedItor->split();
1467             trimmedItor = trimmedWordList.erase(trimmedItor);
1468             trimmedItor = trimmedWordList.insert(trimmedItor, result.begin(), result.end());
1469             std::advance(trimmedItor, result.size());
1470         }
1471 
1472         if (trimmedWordList.size() == 0) {
1473             continue;
1474         }
1475 
1476         it = clusters.erase(it);
1477         it = clusters.insert(it, trimmedWordList.begin(), trimmedWordList.end());
1478         std::advance(it, trimmedWordList.size() - 1);
1479     }
1480 }
1481 
1482 void TextWrapper::generateTextLines(SkScalar maxWidth,
1483                                     const AddLineToParagraph& addLine,
1484                                     const SkSpan<Cluster>& span) {
1485     initializeFormattingState(maxWidth, span);
1486     processLineStretches(maxWidth, addLine);
1487     finalizeTextLayout(addLine);
1488 }
1489 
1490 void TextWrapper::initializeFormattingState(SkScalar maxWidth, const SkSpan<Cluster>& span) {
1491     const auto& style = fParent->paragraphStyle();
1492     fFormattingContext = {style.getMaxLines() == std::numeric_limits<size_t>::max(),
1493                           !SkIsFinite(maxWidth),
1494                           style.ellipsized(),
1495                           style.getTextHeightBehavior() & TextHeightBehavior::kDisableFirstAscent,
1496                           style.getTextHeightBehavior() & TextHeightBehavior::kDisableLastDescent,
1497                           style.getMaxLines(),
1498                           style.effective_align()};
1499 
1500     fFirstLine = true;
1501     fSoftLineMaxIntrinsicWidth = 0;
1502     fNoIndentWidth = maxWidth;
1503     fEnd = span.end() - 1;
1504     fStart = span.begin();
1505 }
1506 
1507 void TextWrapper::processLineStretches(SkScalar maxWidth, const AddLineToParagraph& addLine) {
1508     for (TextStretch& line : fLineStretches) {
1509         prepareLineForFormatting(line);
1510         formatCurrentLine(addLine);
1511 
1512         advanceToNextLine();
1513         if (shouldBreakFormattingLoop()) {
1514             break;
1515         }
1516         ++fLineNumber;
1517     }
1518 }
1519 
1520 void TextWrapper::finalizeTextLayout(const AddLineToParagraph& addLine) {
1521     processRemainingClusters();
1522     addFinalLineBreakIfNeeded(addLine);
1523     adjustFirstLastLineMetrics();
1524 
1525     if (fParent->paragraphStyle().getIsEndAddParagraphSpacing() &&
1526         fParent->paragraphStyle().getParagraphSpacing() > 0) {
1527         fHeight += fParent->paragraphStyle().getParagraphSpacing();
1528     }
1529 }
1530 
1531 void TextWrapper::prepareLineForFormatting(TextStretch& line) {
1532     fEndLine = std::move(line);
1533     fHardLineBreak = fEndLine.endCluster()->isHardBreak();
1534 }
1535 
1536 void TextWrapper::formatCurrentLine(const AddLineToParagraph& addLine) {
1537     bool needEllipsis = determineIfEllipsisNeeded();
1538     trimLineSpaces();
1539     handleSpecialCases(needEllipsis);
1540     updateLineMetrics();
1541     addFormattedLineToParagraph(addLine, needEllipsis);
1542 }
1543 
1544 bool TextWrapper::determineIfEllipsisNeeded() {
1545     bool lastLine = (fFormattingContext.hasEllipsis && fFormattingContext.unlimitedLines) ||
1546                     fLineNumber >= fFormattingContext.maxLines;
1547     bool needEllipsis =
1548             fFormattingContext.hasEllipsis && !fFormattingContext.endlessLine && lastLine;
1549 
1550     if (fParent->paragraphStyle().getEllipsisMod() == EllipsisModal::HEAD &&
1551         fFormattingContext.hasEllipsis) {
1552         needEllipsis = fFormattingContext.maxLines <= 1;
1553         if (needEllipsis) {
1554             fHardLineBreak = false;
1555         }
1556     }
1557 
1558     return needEllipsis;
1559 }
1560 
1561 void TextWrapper::trimLineSpaces() {
1562     this->trimEndSpaces(fFormattingContext.align);
1563     std::tie(fCurrentStartLine, fCurrentStartPos, fCurrentLineWidthWithSpaces) =
1564             this->trimStartSpaces(fEnd);
1565 }
1566 
1567 void TextWrapper::handleSpecialCases(bool needEllipsis) {
1568     if (needEllipsis && !fHardLineBreak) {
1569         fEndLine.restoreBreak();
1570         fCurrentLineWidthWithSpaces = fEndLine.widthWithGhostSpaces();
1571     } else if (fBrokeLineWithHyphen) {
1572         fEndLine.shiftWidth(fEndLine.endCluster()->width());
1573     }
1574 }
1575 
1576 void TextWrapper::updateLineMetrics() {
1577     if (fEndLine.metrics().isClean()) {
1578         fEndLine.setMetrics(fParent->getEmptyMetrics());
1579     }
1580     updatePlaceholderMetrics();
1581     adjustLineMetricsForFirstLastLine();
1582     applyStrutMetrics();
1583 }
1584 
1585 void TextWrapper::updatePlaceholderMetrics() {
1586     std::vector<Run*> runs;
1587     updateMetricsWithPlaceholder(runs, true);
1588     updateMetricsWithPlaceholder(runs, false);
1589     fMaxRunMetrics = fEndLine.metrics();
1590     fMaxRunMetrics.fForceStrut = false;
1591 }
1592 
1593 void TextWrapper::adjustLineMetricsForFirstLastLine() {
1594     if (fFormattingContext.disableFirstAscent && fFirstLine) {
1595         fEndLine.metrics().fAscent = fEndLine.metrics().fRawAscent;
1596     }
1597     if (fFormattingContext.disableLastDescent && isLastLine()) {
1598         fEndLine.metrics().fDescent = fEndLine.metrics().fRawDescent;
1599     }
1600 }
1601 
1602 void TextWrapper::applyStrutMetrics() {
1603     if (fParent->strutEnabled()) {
1604         fParent->strutMetrics().updateLineMetrics(fEndLine.metrics());
1605     }
1606 }
1607 
1608 TextWrapper::LineTextRanges TextWrapper::calculateLineTextRanges() {
1609     LineTextRanges ranges;
1610 
1611     ranges.textExcludingSpaces = TextRange(fEndLine.startCluster()->textRange().start,
1612                                            fEndLine.endCluster()->textRange().end);
1613 
1614     ranges.text = TextRange(fEndLine.startCluster()->textRange().start,
1615                             fEndLine.breakCluster()->textRange().start);
1616 
1617     ranges.textIncludingNewlines = TextRange(fEndLine.startCluster()->textRange().start,
1618                                              fCurrentStartLine->textRange().start);
1619 
1620     if (fCurrentStartLine == fEnd) {
1621         ranges.textIncludingNewlines.end = fParent->text().size();
1622         ranges.text.end = fParent->text().size();
1623     }
1624 
1625     ranges.clusters =
1626             ClusterRange(fEndLine.startCluster() - fStart, fEndLine.endCluster() - fStart + 1);
1627 
1628     ranges.clustersWithGhosts =
1629             ClusterRange(fEndLine.startCluster() - fStart, fCurrentStartLine - fStart);
1630 
1631     if (fEndLine.empty()) {
1632         ranges.textExcludingSpaces.end = ranges.textExcludingSpaces.start;
1633         ranges.clusters.end = ranges.clusters.start;
1634     }
1635 
1636     ranges.text.end = std::max(ranges.text.end, ranges.textExcludingSpaces.end);
1637 
1638     return ranges;
1639 }
1640 
1641 SkScalar TextWrapper::calculateLineHeight() {
1642     SkScalar height = fEndLine.metrics().height();
1643     if (fHardLineBreak && !isLastLine() && fParent->paragraphStyle().getParagraphSpacing() > 0) {
1644         height += fParent->paragraphStyle().getParagraphSpacing();
1645     }
1646     return height;
1647 }
1648 
1649 void TextWrapper::addFormattedLineToParagraph(const AddLineToParagraph& addLine,
1650                                               bool needEllipsis) {
1651     LineTextRanges ranges = calculateLineTextRanges();
1652     SkScalar lineHeight = calculateLineHeight();
1653     SkScalar offsetX = fParent->detectIndents(fLineNumber - 1);
1654 
1655     addLine(ranges.textExcludingSpaces,
1656             ranges.text,
1657             ranges.textIncludingNewlines,
1658             ranges.clusters,
1659             ranges.clustersWithGhosts,
1660             fCurrentLineWidthWithSpaces,
1661             fEndLine.startPos(),
1662             fEndLine.endPos(),
1663             SkVector::Make(offsetX, fHeight),
1664             SkVector::Make(fEndLine.width(), lineHeight),
1665             fEndLine.metrics(),
1666             needEllipsis,
1667             offsetX,
1668             fNoIndentWidth);
1669 
1670     updateIntrinsicWidths();
1671 }
1672 
1673 void TextWrapper::updateIntrinsicWidths() {
1674     fSoftLineMaxIntrinsicWidth += fCurrentLineWidthWithSpaces;
1675     fMaxIntrinsicWidth = std::max(fMaxIntrinsicWidth, fSoftLineMaxIntrinsicWidth);
1676     if (fHardLineBreak) {
1677         fSoftLineMaxIntrinsicWidth = 0;
1678     }
1679 }
1680 
1681 bool TextWrapper::shouldBreakFormattingLoop() {
1682     if (fFormattingContext.hasEllipsis && fFormattingContext.unlimitedLines) {
1683         if (!fHardLineBreak) {
1684             return true;
1685         }
1686     } else if (isLastLine()) {
1687         fHardLineBreak = false;
1688         return true;
1689     }
1690     return false;
1691 }
1692 
1693 bool TextWrapper::isLastLine() const { return fLineNumber >= fFormattingContext.maxLines; }
1694 
1695 void TextWrapper::advanceToNextLine() {
1696     prepareForNextLine();
1697     fFirstLine = false;
1698 }
1699 
1700 void TextWrapper::prepareForNextLine() {
1701     fHeight += calculateLineHeight();
1702     if (!fHardLineBreak || fCurrentStartLine != fEnd) {
1703         fEndLine.clean();
1704     }
1705     fEndLine.startFrom(fCurrentStartLine, fCurrentStartPos);
1706     fParent->fMaxWidthWithTrailingSpaces =
1707             std::max(fParent->fMaxWidthWithTrailingSpaces, fCurrentLineWidthWithSpaces);
1708 }
1709 
1710 void TextWrapper::processRemainingClusters() {
1711     if (fEndLine.endCluster() == nullptr) {
1712         return;
1713     }
1714 
1715     float lastWordLength = 0.0f;
1716     auto cluster = fEndLine.endCluster();
1717 
1718     while (cluster != fEnd || cluster->endPos() < fEnd->endPos()) {
1719         fExceededMaxLines = true;
1720 
1721         if (cluster->isHardBreak()) {
1722             handleHardBreak(lastWordLength);
1723         } else if (cluster->isWhitespaceBreak()) {
1724             handleWhitespaceBreak(cluster, lastWordLength);
1725         } else if (cluster->run().isPlaceholder()) {
1726             handlePlaceholder(cluster, lastWordLength);
1727         } else {
1728             handleRegularCluster(cluster, lastWordLength);
1729         }
1730 
1731         ++cluster;
1732     }
1733 
1734     fMinIntrinsicWidth = std::max(fMinIntrinsicWidth, lastWordLength);
1735     fMaxIntrinsicWidth = std::max(fMaxIntrinsicWidth, fSoftLineMaxIntrinsicWidth);
1736 
1737     if (fParent->lines().empty()) {
1738         adjustMetricsForEmptyParagraph();
1739         fHeight = std::max(fHeight, fEndLine.metrics().height());
1740     }
1741 }
1742 
1743 void TextWrapper::handleHardBreak(float& lastWordLength) {
1744     fMaxIntrinsicWidth = std::max(fMaxIntrinsicWidth, fSoftLineMaxIntrinsicWidth);
1745     fSoftLineMaxIntrinsicWidth = 0;
1746     fMinIntrinsicWidth = std::max(fMinIntrinsicWidth, lastWordLength);
1747     lastWordLength = 0;
1748 }
1749 
1750 void TextWrapper::handleWhitespaceBreak(Cluster* cluster, float& lastWordLength) {
1751     fSoftLineMaxIntrinsicWidth += cluster->width();
1752     fMinIntrinsicWidth = std::max(fMinIntrinsicWidth, lastWordLength);
1753     lastWordLength = 0;
1754 }
1755 
1756 void TextWrapper::handlePlaceholder(Cluster* cluster, float& lastWordLength) {
1757     fMinIntrinsicWidth = std::max(fMinIntrinsicWidth, lastWordLength);
1758     fSoftLineMaxIntrinsicWidth += cluster->width();
1759     fMinIntrinsicWidth = std::max(fMinIntrinsicWidth, cluster->width());
1760     lastWordLength = 0;
1761 }
1762 
1763 void TextWrapper::handleRegularCluster(Cluster* cluster, float& lastWordLength) {
1764     fSoftLineMaxIntrinsicWidth += cluster->width();
1765     lastWordLength += cluster->width();
1766 }
1767 
1768 void TextWrapper::adjustMetricsForEmptyParagraph() {
1769     if (fFormattingContext.disableFirstAscent) {
1770         fEndLine.metrics().fAscent = fEndLine.metrics().fRawAscent;
1771     }
1772     if (fFormattingContext.disableLastDescent && !fHardLineBreak) {
1773         fEndLine.metrics().fDescent = fEndLine.metrics().fRawDescent;
1774     }
1775 }
1776 
1777 void TextWrapper::addFinalLineBreakIfNeeded(const AddLineToParagraph& addLine) {
1778     if (!fHardLineBreak) {
1779         return;
1780     }
1781 
1782     if (fFormattingContext.disableLastDescent) {
1783         fEndLine.metrics().fDescent = fEndLine.metrics().fRawDescent;
1784     }
1785 
1786     if (fParent->strutEnabled()) {
1787         fParent->strutMetrics().updateLineMetrics(fEndLine.metrics());
1788     }
1789 
1790     ClusterRange clusters(fEndLine.breakCluster() - fStart, fEndLine.endCluster() - fStart);
1791 
1792     addLine(fEndLine.breakCluster()->textRange(),
1793             fEndLine.breakCluster()->textRange(),
1794             fEndLine.endCluster()->textRange(),
1795             clusters,
1796             clusters,
1797             0,
1798             0,
1799             0,
1800             SkVector::Make(0, fHeight),
1801             SkVector::Make(0, fEndLine.metrics().height()),
1802             fEndLine.metrics(),
1803             false,
1804             fParent->detectIndents(fLineNumber - 1),
1805             fNoIndentWidth);
1806 
1807     fHeight += fEndLine.metrics().height();
1808     fParent->lines().back().setMaxRunMetrics(fMaxRunMetrics);
1809 }
1810 
1811 void TextWrapper::adjustFirstLastLineMetrics() {
1812     if (fParent->lines().empty()) {
1813         return;
1814     }
1815 
1816     if (fFormattingContext.disableFirstAscent) {
1817         fParent->lines().front().setAscentStyle(LineMetricStyle::Typographic);
1818     }
1819 
1820     if (fFormattingContext.disableLastDescent) {
1821         fParent->lines().back().setDescentStyle(LineMetricStyle::Typographic);
1822     }
1823 }
1824 
1825 void TextWrapper::preProcessingForLineStretches() {
1826     if (fLineStretches.empty()) {
1827         return;
1828     }
1829 
1830     const ParagraphStyle& style = fParent->getParagraphStyle();
1831     EllipsisModal ellipsisMod = style.getEllipsisMod();
1832     if (style.getMaxLines() == 1 && fLineStretches.size() > 1 &&
1833         (ellipsisMod == EllipsisModal::HEAD || ellipsisMod == EllipsisModal::MIDDLE)) {
1834         TextStretch merged = fLineStretches.front();
1835 
1836         for (size_t i = 1; i < fLineStretches.size(); ++i) {
1837             merged.extend(fLineStretches[i]);
1838         }
1839         fLineStretches.clear();
1840         fLineStretches.push_back(merged);
1841     }
1842 }
1843 
1844 void TextWrapper::layoutLinesBalanced(ParagraphImpl* parent,
1845                                       SkScalar maxWidth,
1846                                       const AddLineToParagraph& addLine) {
1847     reset();
1848     SkSpan<Cluster> span = parent->clusters();
1849     if (span.empty()) {
1850         return;
1851     }
1852 
1853     // Generate word stretches
1854     generateWordStretches(span, parent->getWordBreakType());
1855 
1856     for (std::vector<TextStretch>& wordStretches : fWordStretchesBatch) {
1857         // Trimming a word that is longer than the line width
1858         formalizedClusters(wordStretches, maxWidth);
1859 
1860         std::vector<SkScalar> clustersWidthVector = generateWordsWidthInfo(wordStretches);
1861 
1862         // Execute the balancing algorithm to generate line grouping information
1863         std::vector<std::pair<size_t, size_t>> linesGroupInfo =
1864                 generateLinesGroupInfo(clustersWidthVector, maxWidth);
1865 
1866         generateLineStretches(linesGroupInfo, wordStretches);
1867     }
1868 
1869     preProcessingForLineStretches();
1870 
1871     generateTextLines(maxWidth, addLine, span);
1872 }
1873 #else
1874 // Since we allow cluster clipping when they don't fit
1875 // we have to work with stretches - parts of clusters
1876 void TextWrapper::lookAhead(SkScalar maxWidth, Cluster* endOfClusters, bool applyRoundingHack) {
1877 
1878     reset();
1879     fEndLine.metrics().clean();
1880     fWords.startFrom(fEndLine.startCluster(), fEndLine.startPos());
1881     fClusters.startFrom(fEndLine.startCluster(), fEndLine.startPos());
1882     fClip.startFrom(fEndLine.startCluster(), fEndLine.startPos());
1883 
1884     LineBreakerWithLittleRounding breaker(maxWidth, applyRoundingHack);
1885     Cluster* nextNonBreakingSpace = nullptr;
1886     for (auto cluster = fEndLine.endCluster(); cluster < endOfClusters; ++cluster) {
1887         if (cluster->isHardBreak()) {
1888         } else if (
1889                 // TODO: Trying to deal with flutter rounding problem. Must be removed...
1890                 SkScalar width = fWords.width() + fClusters.width() + cluster->width();
1891                 breaker.breakLine(width)) {
1892             if (cluster->isWhitespaceBreak()) {
1893                 // It's the end of the word
1894                 fClusters.extend(cluster);
1895                 fMinIntrinsicWidth = std::max(fMinIntrinsicWidth, this->getClustersTrimmedWidth());
1896                 fWords.extend(fClusters);
1897                 continue;
1898             } else if (cluster->run().isPlaceholder()) {
1899                 if (!fClusters.empty()) {
1900                     // Placeholder ends the previous word
1901                     fMinIntrinsicWidth = std::max(fMinIntrinsicWidth, this->getClustersTrimmedWidth());
1902                     fWords.extend(fClusters);
1903                 }
1904 
1905                 if (cluster->width() > maxWidth && fWords.empty()) {
1906                     // Placeholder is the only text and it's longer than the line;
1907                     // it does not count in fMinIntrinsicWidth
1908                     fClusters.extend(cluster);
1909                     fTooLongCluster = true;
1910                     fTooLongWord = true;
1911                 } else {
1912                     // Placeholder does not fit the line; it will be considered again on the next line
1913                 }
1914                 break;
1915             }
1916 
1917             // Walk further to see if there is a too long word, cluster or glyph
1918             SkScalar nextWordLength = fClusters.width();
1919             SkScalar nextShortWordLength = nextWordLength;
1920             for (auto further = cluster; further != endOfClusters; ++further) {
1921                 if (further->isSoftBreak() || further->isHardBreak() || further->isWhitespaceBreak()) {
1922                     break;
1923                 }
1924                 if (further->run().isPlaceholder()) {
1925                   // Placeholder ends the word
1926                   break;
1927                 }
1928 
1929                 if (nextWordLength > 0 && nextWordLength <= maxWidth && further->isIntraWordBreak()) {
1930                     // The cluster is spaces but not the end of the word in a normal sense
1931                     nextNonBreakingSpace = further;
1932                     nextShortWordLength = nextWordLength;
1933                 }
1934 
1935                 if (maxWidth == 0) {
1936                     // This is a tricky flutter case: layout(width:0) places 1 cluster on each line
1937                     nextWordLength = std::max(nextWordLength, further->width());
1938                 } else {
1939                     nextWordLength += further->width();
1940                 }
1941             }
1942             if (nextWordLength > maxWidth) {
1943                 if (nextNonBreakingSpace != nullptr) {
1944                     // We only get here if the non-breaking space improves our situation
1945                     // (allows us to break the text to fit the word)
1946                     if (SkScalar shortLength = fWords.width() + nextShortWordLength;
1947                         !breaker.breakLine(shortLength)) {
1948                         // We can add the short word to the existing line
1949                         fClusters = TextStretch(fClusters.startCluster(), nextNonBreakingSpace, fClusters.metrics().getForceStrut());
1950                         fMinIntrinsicWidth = std::max(fMinIntrinsicWidth, nextShortWordLength);
1951                         fWords.extend(fClusters);
1952                     } else {
1953                         // We can place the short word on the next line
1954                         fClusters.clean();
1955                     }
1956                     // Either way we are not in "word is too long" situation anymore
1957                     break;
1958                 }
1959                 // If the word is too long we can break it right now and hope it's enough
1960                 fMinIntrinsicWidth = std::max(fMinIntrinsicWidth, nextWordLength);
1961                 if (fClusters.endPos() - fClusters.startPos() > 1 ||
1962                     fWords.empty()) {
1963                     fTooLongWord = true;
1964                 } else {
1965                     // Even if the word is too long there is a very little space on this line.
1966                     // let's deal with it on the next line.
1967                 }
1968             }
1969 
1970             if (cluster->width() > maxWidth) {
1971                 fClusters.extend(cluster);
1972                 fTooLongCluster = true;
1973                 fTooLongWord = true;
1974             }
1975             break;
1976         }
1977 
1978         if (cluster->run().isPlaceholder()) {
1979             if (!fClusters.empty()) {
1980                 // Placeholder ends the previous word (placeholders are ignored in trimming)
1981                 fMinIntrinsicWidth = std::max(fMinIntrinsicWidth, getClustersTrimmedWidth());
1982                 fWords.extend(fClusters);
1983             }
1984 
1985             // Placeholder is separate word and its width now is counted in minIntrinsicWidth
1986             fMinIntrinsicWidth = std::max(fMinIntrinsicWidth, cluster->width());
1987             fWords.extend(cluster);
1988         } else {
1989             fClusters.extend(cluster);
1990 
1991             // Keep adding clusters/words
1992             if (fClusters.endOfWord()) {
1993                 fMinIntrinsicWidth = std::max(fMinIntrinsicWidth, getClustersTrimmedWidth());
1994                 fWords.extend(fClusters);
1995             }
1996         }
1997 
1998         if ((fHardLineBreak = cluster->isHardBreak())) {
1999             // Stop at the hard line break
2000             break;
2001         }
2002     }
2003 }
2004 
2005 void TextWrapper::moveForward(bool hasEllipsis) {
2006 
2007     // We normally break lines by words.
2008     // The only way we may go to clusters is if the word is too long or
2009     // it's the first word and it has an ellipsis attached to it.
2010     // If nothing fits we show the clipping.
2011     if (!fWords.empty()) {
2012         fEndLine.extend(fWords);
2013 #ifdef SK_IGNORE_SKPARAGRAPH_ELLIPSIS_FIX
2014         if (!fTooLongWord || hasEllipsis) { // Ellipsis added to a word
2015 #else
2016         if (!fTooLongWord && !hasEllipsis) { // Ellipsis added to a grapheme
2017 #endif
2018             return;
2019         }
2020     }
2021     if (!fClusters.empty()) {
2022         fEndLine.extend(fClusters);
2023         if (!fTooLongCluster) {
2024             return;
2025         }
2026     }
2027 
2028     if (!fClip.empty()) {
2029         // Flutter: forget the clipped cluster but keep the metrics
2030         fEndLine.metrics().add(fClip.metrics());
2031     }
2032 }
2033 
2034 // Special case for start/end cluster since they can be clipped
2035 void TextWrapper::trimEndSpaces(TextAlign align) {
2036     // Remember the breaking position
2037     fEndLine.saveBreak();
2038     // Skip all space cluster at the end
2039     for (auto cluster = fEndLine.endCluster();
2040          cluster >= fEndLine.startCluster() && cluster->isWhitespaceBreak();
2041          --cluster) {
2042         fEndLine.trim(cluster);
2043     }
2044     fEndLine.trim();
2045 }
2046 
2047 SkScalar TextWrapper::getClustersTrimmedWidth() {
2048     // Move the end of the line to the left
2049     SkScalar width = 0;
2050     bool trailingSpaces = true;
2051     for (auto cluster = fClusters.endCluster(); cluster >= fClusters.startCluster(); --cluster) {
2052         if (cluster->run().isPlaceholder()) {
2053             continue;
2054         }
2055         if (trailingSpaces) {
2056             if (!cluster->isWhitespaceBreak()) {
2057                 width += cluster->trimmedWidth(cluster->endPos());
2058                 trailingSpaces = false;
2059             }
2060             continue;
2061         }
2062         width += cluster->width();
2063     }
2064     return width;
2065 }
2066 
2067 // Trim the beginning spaces in case of soft line break
2068 std::tuple<Cluster*, size_t, SkScalar> TextWrapper::trimStartSpaces(Cluster* endOfClusters) {
2069 
2070     if (fHardLineBreak) {
2071         // End of line is always end of cluster, but need to skip \n
2072         auto width = fEndLine.width();
2073         auto cluster = fEndLine.endCluster() + 1;
2074         while (cluster < fEndLine.breakCluster() && cluster->isWhitespaceBreak())  {
2075             width += cluster->width();
2076             ++cluster;
2077         }
2078         return std::make_tuple(fEndLine.breakCluster() + 1, 0, width);
2079     }
2080 
2081     // breakCluster points to the end of the line;
2082     // It's a soft line break so we need to move lineStart forward skipping all the spaces
2083     auto width = fEndLine.widthWithGhostSpaces();
2084     auto cluster = fEndLine.breakCluster() + 1;
2085     while (cluster < endOfClusters && cluster->isWhitespaceBreak()) {
2086         width += cluster->width();
2087         ++cluster;
2088     }
2089 
2090     if (fEndLine.breakCluster()->isWhitespaceBreak() && fEndLine.breakCluster() < endOfClusters) {
2091         // In case of a soft line break by the whitespace
2092         // fBreak should point to the beginning of the next line
2093         // (it only matters when there are trailing spaces)
2094         fEndLine.shiftBreak();
2095     }
2096 
2097     return std::make_tuple(cluster, 0, width);
2098 }
2099 
2100 // TODO: refactor the code for line ending (with/without ellipsis)
2101 void TextWrapper::breakTextIntoLines(ParagraphImpl* parent,
2102                                      SkScalar maxWidth,
2103                                      const AddLineToParagraph& addLine) {
2104     fHeight = 0;
2105     fMinIntrinsicWidth = std::numeric_limits<SkScalar>::min();
2106     fMaxIntrinsicWidth = std::numeric_limits<SkScalar>::min();
2107 
2108     auto span = parent->clusters();
2109     if (span.empty()) {
2110         return;
2111     }
2112     auto maxLines = parent->paragraphStyle().getMaxLines();
2113     auto align = parent->paragraphStyle().effective_align();
2114     auto unlimitedLines = maxLines == std::numeric_limits<size_t>::max();
2115     auto endlessLine = !SkIsFinite(maxWidth);
2116     auto hasEllipsis = parent->paragraphStyle().ellipsized();
2117 
2118     auto disableFirstAscent = parent->paragraphStyle().getTextHeightBehavior() & TextHeightBehavior::kDisableFirstAscent;
2119     auto disableLastDescent = parent->paragraphStyle().getTextHeightBehavior() & TextHeightBehavior::kDisableLastDescent;
2120     bool firstLine = true; // We only interested in fist line if we have to disable the first ascent
2121 
2122     SkScalar softLineMaxIntrinsicWidth = 0;
2123     fEndLine = TextStretch(span.begin(), span.begin(), parent->strutForceHeight());
2124     auto end = span.end() - 1;
2125     auto start = span.begin();
2126     InternalLineMetrics maxRunMetrics;
2127     bool needEllipsis = false;
2128     while (fEndLine.endCluster() != end) {
2129 
2130         this->lookAhead(maxWidth, end, parent->getApplyRoundingHack());
2131 
2132         auto lastLine = (hasEllipsis && unlimitedLines) || fLineNumber >= maxLines;
2133         needEllipsis = hasEllipsis && !endlessLine && lastLine;
2134 
2135         this->moveForward(needEllipsis);
2136         needEllipsis &= fEndLine.endCluster() < end - 1; // Only if we have some text to ellipsize
2137 
2138         // Do not trim end spaces on the naturally last line of the left aligned text
2139         this->trimEndSpaces(align);
2140 
2141         // For soft line breaks add to the line all the spaces next to it
2142         Cluster* startLine;
2143         size_t pos;
2144         SkScalar widthWithSpaces;
2145         std::tie(startLine, pos, widthWithSpaces) = this->trimStartSpaces(end);
2146 
2147         if (needEllipsis && !fHardLineBreak) {
2148             // This is what we need to do to preserve a space before the ellipsis
2149             fEndLine.restoreBreak();
2150             widthWithSpaces = fEndLine.widthWithGhostSpaces();
2151         }
2152 
2153         // If the line is empty with the hard line break, let's take the paragraph font (flutter???)
2154         if (fEndLine.metrics().isClean()) {
2155             fEndLine.setMetrics(parent->getEmptyMetrics());
2156         }
2157 
2158         // Deal with placeholder clusters == runs[@size==1]
2159         Run* lastRun = nullptr;
2160         for (auto cluster = fEndLine.startCluster(); cluster <= fEndLine.endCluster(); ++cluster) {
2161             auto r = cluster->runOrNull();
2162             if (r == lastRun) {
2163                 continue;
2164             }
2165             lastRun = r;
2166             if (lastRun->placeholderStyle() != nullptr) {
2167                 SkASSERT(lastRun->size() == 1);
2168                 // Update the placeholder metrics so we can get the placeholder positions later
2169                 // and the line metrics (to make sure the placeholder fits)
2170                 lastRun->updateMetrics(&fEndLine.metrics());
2171             }
2172         }
2173 
2174         // Before we update the line metrics with struts,
2175         // let's save it for GetRectsForRange(RectHeightStyle::kMax)
2176         maxRunMetrics = fEndLine.metrics();
2177         maxRunMetrics.fForceStrut = false;
2178 
2179         TextRange textExcludingSpaces(fEndLine.startCluster()->textRange().start, fEndLine.endCluster()->textRange().end);
2180         TextRange text(fEndLine.startCluster()->textRange().start, fEndLine.breakCluster()->textRange().start);
2181         TextRange textIncludingNewlines(fEndLine.startCluster()->textRange().start, startLine->textRange().start);
2182         if (startLine == end) {
2183             textIncludingNewlines.end = parent->text().size();
2184             text.end = parent->text().size();
2185         }
2186         ClusterRange clusters(fEndLine.startCluster() - start, fEndLine.endCluster() - start + 1);
2187         ClusterRange clustersWithGhosts(fEndLine.startCluster() - start, startLine - start);
2188 
2189         if (disableFirstAscent && firstLine) {
2190             fEndLine.metrics().fAscent = fEndLine.metrics().fRawAscent;
2191         }
2192         if (disableLastDescent && (lastLine || (startLine == end && !fHardLineBreak ))) {
2193             fEndLine.metrics().fDescent = fEndLine.metrics().fRawDescent;
2194         }
2195 
2196         if (parent->strutEnabled()) {
2197             // Make sure font metrics are not less than the strut
2198             parent->strutMetrics().updateLineMetrics(fEndLine.metrics());
2199         }
2200 
2201         SkScalar lineHeight = fEndLine.metrics().height();
2202         firstLine = false;
2203 
2204         if (fEndLine.empty()) {
2205             // Correct text and clusters (make it empty for an empty line)
2206             textExcludingSpaces.end = textExcludingSpaces.start;
2207             clusters.end = clusters.start;
2208         }
2209 
2210         // In case of a force wrapping we don't have a break cluster and have to use the end cluster
2211         text.end = std::max(text.end, textExcludingSpaces.end);
2212 
2213         addLine(textExcludingSpaces,
2214                 text,
2215                 textIncludingNewlines, clusters, clustersWithGhosts, widthWithSpaces,
2216                 fEndLine.startPos(),
2217                 fEndLine.endPos(),
2218                 SkVector::Make(0, fHeight),
2219                 SkVector::Make(fEndLine.width(), lineHeight),
2220                 fEndLine.metrics(),
2221                 needEllipsis && !fHardLineBreak);
2222 
2223         softLineMaxIntrinsicWidth += widthWithSpaces;
2224 
2225         fMaxIntrinsicWidth = std::max(fMaxIntrinsicWidth, softLineMaxIntrinsicWidth);
2226         if (fHardLineBreak) {
2227             softLineMaxIntrinsicWidth = 0;
2228         }
2229         // Start a new line
2230         fHeight += lineHeight;
2231         if (!fHardLineBreak || startLine != end) {
2232             fEndLine.clean();
2233         }
2234         fEndLine.startFrom(startLine, pos);
2235         parent->fMaxWidthWithTrailingSpaces = std::max(parent->fMaxWidthWithTrailingSpaces, widthWithSpaces);
2236 
2237         if (hasEllipsis && unlimitedLines) {
2238             // There is one case when we need an ellipsis on a separate line
2239             // after a line break when width is infinite
2240             if (!fHardLineBreak) {
2241                 break;
2242             }
2243         } else if (lastLine) {
2244             // There is nothing more to draw
2245             fHardLineBreak = false;
2246             break;
2247         }
2248 
2249         ++fLineNumber;
2250     }
2251 
2252     // We finished formatting the text but we need to scan the rest for some numbers
2253     if (fEndLine.endCluster() != nullptr) {
2254         auto lastWordLength = 0.0f;
2255         auto cluster = fEndLine.endCluster();
2256         while (cluster != end || cluster->endPos() < end->endPos()) {
2257             fExceededMaxLines = true;
2258             if (cluster->isHardBreak()) {
2259                 // Hard line break ends the word and the line
2260                 fMaxIntrinsicWidth = std::max(fMaxIntrinsicWidth, softLineMaxIntrinsicWidth);
2261                 softLineMaxIntrinsicWidth = 0;
2262                 fMinIntrinsicWidth = std::max(fMinIntrinsicWidth, lastWordLength);
2263                 lastWordLength = 0;
2264             } else if (cluster->isWhitespaceBreak()) {
2265                 // Whitespaces end the word
2266                 softLineMaxIntrinsicWidth += cluster->width();
2267                 fMinIntrinsicWidth = std::max(fMinIntrinsicWidth, lastWordLength);
2268                 lastWordLength = 0;
2269             } else if (cluster->run().isPlaceholder()) {
2270                 // Placeholder ends the previous word and creates a separate one
2271                 fMinIntrinsicWidth = std::max(fMinIntrinsicWidth, lastWordLength);
2272                 // Placeholder width now counts in fMinIntrinsicWidth
2273                 softLineMaxIntrinsicWidth += cluster->width();
2274                 fMinIntrinsicWidth = std::max(fMinIntrinsicWidth, cluster->width());
2275                 lastWordLength = 0;
2276             } else {
2277                 // Nothing out of ordinary - just add this cluster to the word and to the line
2278                 softLineMaxIntrinsicWidth += cluster->width();
2279                 lastWordLength += cluster->width();
2280             }
2281             ++cluster;
2282         }
2283         fMinIntrinsicWidth = std::max(fMinIntrinsicWidth, lastWordLength);
2284         fMaxIntrinsicWidth = std::max(fMaxIntrinsicWidth, softLineMaxIntrinsicWidth);
2285 
2286         if (parent->lines().empty()) {
2287             // In case we could not place even a single cluster on the line
2288             if (disableFirstAscent) {
2289                 fEndLine.metrics().fAscent = fEndLine.metrics().fRawAscent;
2290             }
2291             if (disableLastDescent && !fHardLineBreak) {
2292                 fEndLine.metrics().fDescent = fEndLine.metrics().fRawDescent;
2293             }
2294             fHeight = std::max(fHeight, fEndLine.metrics().height());
2295         }
2296     }
2297 
2298     if (fHardLineBreak) {
2299         if (disableLastDescent) {
2300             fEndLine.metrics().fDescent = fEndLine.metrics().fRawDescent;
2301         }
2302 
2303         // Last character is a line break
2304         if (parent->strutEnabled()) {
2305             // Make sure font metrics are not less than the strut
2306             parent->strutMetrics().updateLineMetrics(fEndLine.metrics());
2307         }
2308 
2309         ClusterRange clusters(fEndLine.breakCluster() - start, fEndLine.endCluster() - start);
2310         addLine(fEndLine.breakCluster()->textRange(),
2311                 fEndLine.breakCluster()->textRange(),
2312                 fEndLine.endCluster()->textRange(),
2313                 clusters,
2314                 clusters,
2315                 0,
2316                 0,
2317                 0,
2318                 SkVector::Make(0, fHeight),
2319                 SkVector::Make(0, fEndLine.metrics().height()),
2320                 fEndLine.metrics(),
2321                 needEllipsis);
2322         fHeight += fEndLine.metrics().height();
2323         parent->lines().back().setMaxRunMetrics(maxRunMetrics);
2324     }
2325 
2326     if (parent->lines().empty()) {
2327         return;
2328     }
2329     // Correct line metric styles for the first and for the last lines if needed
2330     if (disableFirstAscent) {
2331         parent->lines().front().setAscentStyle(LineMetricStyle::Typographic);
2332     }
2333     if (disableLastDescent) {
2334         parent->lines().back().setDescentStyle(LineMetricStyle::Typographic);
2335     }
2336 }
2337 #endif
2338 }  // namespace textlayout
2339 }  // namespace skia
2340