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