• 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 "log.h"
7 #include "modules/skparagraph/include/Hyphenator.h"
8 #include "modules/skparagraph/src/TextTabAlign.h"
9 #include "TextParameter.h"
10 #endif
11 
12 namespace skia {
13 namespace textlayout {
14 
15 namespace {
16 const size_t BREAK_NUM_TWO = 2;
17 
18 struct LineBreakerWithLittleRounding {
LineBreakerWithLittleRoundingskia::textlayout::__anon700d5ab20111::LineBreakerWithLittleRounding19     LineBreakerWithLittleRounding(SkScalar maxWidth, bool applyRoundingHack)
20         : fLower(maxWidth - 0.25f)
21         , fMaxWidth(maxWidth)
22         , fUpper(maxWidth + 0.25f)
23         , fApplyRoundingHack(applyRoundingHack) {}
24 
breakLineskia::textlayout::__anon700d5ab20111::LineBreakerWithLittleRounding25     bool breakLine(SkScalar width) const {
26         if (width < fLower) {
27             return false;
28         } else if (width > fUpper) {
29             return true;
30         }
31 
32         auto val = std::fabs(width);
33         SkScalar roundedWidth;
34         if (fApplyRoundingHack) {
35             if (val < 10000) {
36                 roundedWidth = SkScalarRoundToScalar(width * 100) * (1.0f/100);
37             } else if (val < 100000) {
38                 roundedWidth = SkScalarRoundToScalar(width *  10) * (1.0f/10);
39             } else {
40                 roundedWidth = SkScalarFloorToScalar(width);
41             }
42         } else {
43             if (val < 10000) {
44                 roundedWidth = SkScalarFloorToScalar(width * 100) * (1.0f/100);
45             } else if (val < 100000) {
46                 roundedWidth = SkScalarFloorToScalar(width *  10) * (1.0f/10);
47             } else {
48                 roundedWidth = SkScalarFloorToScalar(width);
49             }
50         }
51         return roundedWidth > fMaxWidth;
52     }
53 
54     const SkScalar fLower, fMaxWidth, fUpper;
55     const bool fApplyRoundingHack;
56 };
57 } // namespace
58 
59 #ifdef OHOS_SUPPORT
matchHyphenResult(const std::vector<uint8_t> & result,ParagraphImpl * owner,size_t & pos,SkScalar maxWidth,SkScalar len)60 void TextWrapper::matchHyphenResult(const std::vector<uint8_t>& result, ParagraphImpl* owner, size_t& pos,
61                                     SkScalar maxWidth, SkScalar len)
62 {
63     auto startPos = pos;
64     size_t ix = 0;
65 
66     // Result array may have more code points than we have visible clusters
67     int32_t prevIx = -1;
68     // cumulatively iterate width vs breaks
69     for (const auto& breakPos : result) {
70         int32_t clusterIx = static_cast<int32_t>(owner->fClustersIndexFromCodeUnit[startPos + ix]);
71         if (clusterIx == prevIx) {
72             ++ix;
73             continue;
74         }
75         prevIx = clusterIx;
76         TEXT_LOGD("hyphen break width:%{public}f / %{public}f : %{public}f", len, maxWidth,
77                   owner->cluster(clusterIx).width());
78         len += owner->cluster(clusterIx).width();
79         auto shouldBreak = (len > maxWidth);
80         if (breakPos & 0x1) {
81             // we need to break after previous char, but the result needs to be mapped
82             pos = startPos + ix;
83         }
84         ++ix;
85         if (shouldBreak) {
86             break;
87         }
88     }
89 }
90 
tryBreakWord(Cluster * startCluster,Cluster * endOfClusters,SkScalar widthBeforeCluster,SkScalar maxWidth)91 size_t TextWrapper::tryBreakWord(Cluster *startCluster, Cluster *endOfClusters,
92                                  SkScalar widthBeforeCluster, SkScalar maxWidth)
93 {
94     auto startPos = startCluster->textRange().start;
95     auto endPos = startPos;
96     auto owner = startCluster->getOwner();
97     for (auto next = startCluster + 1; next != endOfClusters; next++) {
98         // find the end boundary of current word (hard/soft/whitespace break)
99         if (next->isWhitespaceBreak() || next->isHardBreak()) {
100             break;
101         } else {
102             endPos = next->textRange().end;
103         }
104     }
105 
106     // Using end position cluster height as an estimate for reserved hyphen width
107     // hyphen may not be shaped at this point. End pos may be non-drawable, so prefer
108     // previous glyph before break
109     auto mappedEnd = owner->fClustersIndexFromCodeUnit[endPos];
110     auto len = widthBeforeCluster + owner->cluster(mappedEnd > 0 ? mappedEnd - 1 : 0).height();
111     // ToDo: We can stop here based on hyhpenation frequency setting
112     // but there is no need to proceed with hyphenation if we don't have space for even hyphen
113     if (std::isnan(len) || len >= maxWidth) {
114         return startCluster->textRange().start;
115     }
116 
117     auto locale = owner->paragraphStyle().getTextStyle().getLocale();
118     auto result = Hyphenator::getInstance().findBreakPositions(locale, owner->fText, startPos, endPos);
119     endPos = startPos;
120     matchHyphenResult(result, owner, endPos, maxWidth, len);
121 
122     return endPos;
123 }
124 
lookAheadByHyphen(Cluster * endOfClusters,SkScalar widthBeforeCluster,SkScalar maxWidth)125 bool TextWrapper::lookAheadByHyphen(Cluster* endOfClusters, SkScalar widthBeforeCluster, SkScalar maxWidth)
126 {
127     auto startCluster = fClusters.startCluster();
128     while (startCluster != endOfClusters && startCluster->isWhitespaceBreak()) {
129         ++startCluster;
130     }
131     if (startCluster == endOfClusters) {
132         return false;
133     }
134     auto endPos = tryBreakWord(startCluster, endOfClusters, widthBeforeCluster - fClusters.width(), maxWidth);
135     // if break position found, set fClusters and fWords accrodingly and break
136     if (endPos > startCluster->textRange().start) {
137         // need to break before the mapped end cluster
138         auto owner = startCluster->getOwner();
139         fClusters = TextStretch(startCluster, &owner->cluster(owner->fClustersIndexFromCodeUnit[endPos]) - 1,
140                                 fClusters.metrics().getForceStrut());
141         fWords.extend(fClusters);
142         fBrokeLineWithHyphen = true;
143         return false;
144     }
145     // else let the existing implementation do its best efforts
146     return true;
147 }
148 
149 // Since we allow cluster clipping when they don't fit
150 // we have to work with stretches - parts of clusters
lookAhead(SkScalar maxWidth,Cluster * endOfClusters,bool applyRoundingHack,WordBreakType wordBreakType,bool needEllipsis)151 void TextWrapper::lookAhead(SkScalar maxWidth, Cluster* endOfClusters, bool applyRoundingHack,
152                             WordBreakType wordBreakType, bool needEllipsis) {
153 
154     reset();
155     fEndLine.metrics().clean();
156     fWords.startFrom(fEndLine.startCluster(), fEndLine.startPos());
157     fClusters.startFrom(fEndLine.startCluster(), fEndLine.startPos());
158     fClip.startFrom(fEndLine.startCluster(), fEndLine.startPos());
159 
160     bool isFirstWord = true;
161     TextTabAlign textTabAlign(endOfClusters->getOwner()->paragraphStyle().getTextTab());
162     textTabAlign.init(maxWidth, endOfClusters);
163 
164     LineBreakerWithLittleRounding breaker(maxWidth, applyRoundingHack);
165     Cluster* nextNonBreakingSpace = nullptr;
166     SkScalar totalFakeSpacing = 0.0;
167     bool attemptedHyphenate = false;
168 
169     for (auto cluster = fEndLine.endCluster(); cluster < endOfClusters; ++cluster) {
170         totalFakeSpacing += (cluster->needAutoSpacing() && cluster != fEndLine.endCluster()) ?
171             (cluster - 1)->getFontSize() / AUTO_SPACING_WIDTH_RATIO : 0;
172         SkScalar widthBeforeCluster = fWords.width() + fClusters.width() + totalFakeSpacing;
173         if (cluster->isHardBreak()) {
174             if (cluster != fEndLine.endCluster()) {
175                 isFirstWord = false;
176             }
177         } else if (
178                 // TODO: Trying to deal with flutter rounding problem. Must be removed...
179                 SkScalar width = cluster->width() + widthBeforeCluster;
180                 (!isFirstWord || wordBreakType != WordBreakType::NORMAL) &&
181                 breaker.breakLine(width)) {
182             // if the hyphenator has already run as balancing algorithm, use the cluster information
183             if (cluster->isHyphenBreak() && !needEllipsis) {
184                 // we dont want to add the current cluster as the hyphenation algorithm marks breaks before a cluster
185                 // however, if we cannot fit anything to a line, we need to break out here
186                 if (fWords.empty() && fClusters.empty()) {
187                     fClusters.extend(cluster);
188                     fTooLongCluster = true;
189                     break;
190                 }
191                 if (!fClusters.empty()) {
192                     fWords.extend(fClusters);
193                     fBrokeLineWithHyphen = true;
194                     break;
195                 }
196                 // let hyphenator try before this if it is enabled
197             } else if (cluster->isWhitespaceBreak() && ((wordBreakType != WordBreakType::BREAK_HYPHEN) ||
198                        (wordBreakType == WordBreakType::BREAK_HYPHEN && attemptedHyphenate && !needEllipsis))) {
199                 // It's the end of the word
200                 isFirstWord = false;
201                 fClusters.extend(cluster);
202 
203                 bool tabAlignRet = false;
204                 if (cluster->isTabulation()) {
205                     tabAlignRet = textTabAlign.processTab(fWords, fClusters, cluster, totalFakeSpacing);
206                 } else {
207                     tabAlignRet = textTabAlign.processEndofWord(fWords, fClusters, cluster, totalFakeSpacing);
208                 }
209                 if (tabAlignRet) {
210                     break;
211                 }
212                 fMinIntrinsicWidth = std::max(fMinIntrinsicWidth, this->getClustersTrimmedWidth());
213                 fWords.extend(fClusters);
214                 continue;
215             } else if (cluster->run().isPlaceholder()) {
216                 isFirstWord = false;
217                 if (!fClusters.empty()) {
218                     // Placeholder ends the previous word
219                     fMinIntrinsicWidth = std::max(fMinIntrinsicWidth, this->getClustersTrimmedWidth());
220                     fWords.extend(fClusters);
221                 }
222 
223                 if (cluster->width() > maxWidth && fWords.empty()) {
224                     // Placeholder is the only text and it's longer than the line;
225                     // it does not count in fMinIntrinsicWidth
226                     fClusters.extend(cluster);
227                     fTooLongCluster = true;
228                     fTooLongWord = true;
229                 } else {
230                     // Placeholder does not fit the line; it will be considered again on the next line
231                 }
232                 break;
233             }
234 
235             // should do this only if hyphenation is enabled
236             if (wordBreakType == WordBreakType::BREAK_HYPHEN && !attemptedHyphenate && !fClusters.empty() &&
237                 !needEllipsis) {
238                 attemptedHyphenate = true;
239                 if (!lookAheadByHyphen(endOfClusters, widthBeforeCluster, breaker.fUpper)) {
240                     break;
241                 }
242             }
243 
244             textTabAlign.processEndofLine(fWords, fClusters, cluster, totalFakeSpacing);
245 
246             // Walk further to see if there is a too long word, cluster or glyph
247             SkScalar nextWordLength = fClusters.width();
248             SkScalar nextShortWordLength = nextWordLength;
249             for (auto further = cluster; further != endOfClusters; ++further) {
250                 if (further->isSoftBreak() || further->isHardBreak() || further->isWhitespaceBreak()) {
251                     break;
252                 }
253                 if (further->run().isPlaceholder()) {
254                   // Placeholder ends the word
255                   break;
256                 }
257 
258                 if (nextWordLength > 0 && nextWordLength <= maxWidth && further->isIntraWordBreak()) {
259                     // The cluster is spaces but not the end of the word in a normal sense
260                     nextNonBreakingSpace = further;
261                     nextShortWordLength = nextWordLength;
262                 }
263 
264                 if (maxWidth == 0) {
265                     // This is a tricky flutter case: layout(width:0) places 1 cluster on each line
266                     nextWordLength = std::max(nextWordLength, further->width());
267                 } else {
268                     nextWordLength += further->width();
269                 }
270             }
271             if (nextWordLength > maxWidth) {
272                 if (nextNonBreakingSpace != nullptr) {
273                     // We only get here if the non-breaking space improves our situation
274                     // (allows us to break the text to fit the word)
275                     if (SkScalar shortLength = fWords.width() + nextShortWordLength;
276                         !breaker.breakLine(shortLength)) {
277                         // We can add the short word to the existing line
278                         fClusters = TextStretch(fClusters.startCluster(), nextNonBreakingSpace, fClusters.metrics().getForceStrut());
279                         fMinIntrinsicWidth = std::max(fMinIntrinsicWidth, nextShortWordLength);
280                         fWords.extend(fClusters);
281                     } else {
282                         // We can place the short word on the next line
283                         fClusters.clean();
284                     }
285                     // Either way we are not in "word is too long" situation anymore
286                     break;
287                 }
288                 // If the word is too long we can break it right now and hope it's enough
289                 fMinIntrinsicWidth = std::max(fMinIntrinsicWidth, nextWordLength);
290                 if (fClusters.endPos() - fClusters.startPos() > 1 ||
291                     fWords.empty()) {
292                     fTooLongWord = true;
293                 } else {
294                     // Even if the word is too long there is a very little space on this line.
295                     // let's deal with it on the next line.
296                 }
297             }
298 
299             if (fWords.empty() && breaker.breakLine(cluster->width())) {
300                 fClusters.extend(cluster);
301                 fTooLongCluster = true;
302                 fTooLongWord = true;
303             }
304             break;
305         }
306 
307         if (cluster->isSoftBreak() || cluster->isWhitespaceBreak()) {
308             isFirstWord = false;
309         }
310 
311         if (cluster->run().isPlaceholder()) {
312             if (!fClusters.empty()) {
313                 // Placeholder ends the previous word (placeholders are ignored in trimming)
314                 fMinIntrinsicWidth = std::max(fMinIntrinsicWidth, getClustersTrimmedWidth());
315                 fWords.extend(fClusters);
316             }
317 
318             // Placeholder is separate word and its width now is counted in minIntrinsicWidth
319             fMinIntrinsicWidth = std::max(fMinIntrinsicWidth, cluster->width());
320             fWords.extend(cluster);
321         } else {
322             if (cluster->isTabulation()) {
323                 if (textTabAlign.processTab(fWords, fClusters, cluster, totalFakeSpacing)) {
324                     break;
325                 }
326                 fClusters.extend(cluster);
327 
328                 fMinIntrinsicWidth = std::max(fMinIntrinsicWidth, getClustersTrimmedWidth());
329                 fWords.extend(fClusters);
330             } else {
331                 fClusters.extend(cluster);
332                 if (fClusters.endOfWord()) { // Keep adding clusters/words
333                     if (textTabAlign.processEndofWord(fWords, fClusters, cluster, totalFakeSpacing)) {
334                         if (wordBreakType == WordBreakType::BREAK_ALL) {
335                             fClusters.trim(cluster);
336                         }
337                         break;
338                     }
339 
340                     fMinIntrinsicWidth = std::max(fMinIntrinsicWidth, getClustersTrimmedWidth());
341                     fWords.extend(fClusters);
342                 } else {
343                     if (textTabAlign.processCluster(fWords, fClusters, cluster, totalFakeSpacing)) {
344                         fClusters.trim(cluster);
345                         break;
346                     }
347                 }
348             }
349         }
350 
351         if ((fHardLineBreak = cluster->isHardBreak())) {
352             // Stop at the hard line break
353             break;
354         }
355     }
356 }
357 
moveForward(bool hasEllipsis,bool breakAll)358 void TextWrapper::moveForward(bool hasEllipsis, bool breakAll) {
359 
360     // We normally break lines by words.
361     // The only way we may go to clusters is if the word is too long or
362     // it's the first word and it has an ellipsis attached to it.
363     // If nothing fits we show the clipping.
364     fTooLongWord = breakAll;
365     if (!fWords.empty()) {
366         fEndLine.extend(fWords);
367 #ifdef SK_IGNORE_SKPARAGRAPH_ELLIPSIS_FIX
368         if (!fTooLongWord || hasEllipsis) { // Ellipsis added to a word
369 #else
370         if (!fTooLongWord && !hasEllipsis) { // Ellipsis added to a grapheme
371 #endif
372             return;
373         }
374     }
375     if (!fClusters.empty()) {
376         fEndLine.extend(fClusters);
377         if (!fTooLongCluster) {
378             return;
379         }
380     }
381 
382     if (!fClip.empty()) {
383         // Flutter: forget the clipped cluster but keep the metrics
384         fEndLine.metrics().add(fClip.metrics());
385     }
386 }
387 
388 // Special case for start/end cluster since they can be clipped
389 void TextWrapper::trimEndSpaces(TextAlign align) {
390     // Remember the breaking position
391     fEndLine.saveBreak();
392     // Skip all space cluster at the end
393     for (auto cluster = fEndLine.endCluster();
394          cluster >= fEndLine.startCluster() && cluster->isWhitespaceBreak();
395          --cluster) {
396         fEndLine.trim(cluster);
397     }
398     fEndLine.trim();
399 }
400 
401 SkScalar TextWrapper::getClustersTrimmedWidth() {
402     // Move the end of the line to the left
403     SkScalar width = 0;
404     bool trailingSpaces = true;
405     for (auto cluster = fClusters.endCluster(); cluster >= fClusters.startCluster(); --cluster) {
406         if (cluster->run().isPlaceholder()) {
407             continue;
408         }
409         if (trailingSpaces) {
410             if (!cluster->isWhitespaceBreak()) {
411                 width += cluster->trimmedWidth(cluster->endPos());
412                 trailingSpaces = false;
413             }
414             continue;
415         }
416         width += cluster->width();
417     }
418     return width;
419 }
420 
421 // Trim the beginning spaces in case of soft line break
422 std::tuple<Cluster*, size_t, SkScalar> TextWrapper::trimStartSpaces(Cluster* endOfClusters) {
423 
424     if (fHardLineBreak) {
425         // End of line is always end of cluster, but need to skip \n
426         auto width = fEndLine.width();
427         auto cluster = fEndLine.endCluster() + 1;
428         while (cluster < fEndLine.breakCluster() && cluster->isWhitespaceBreak())  {
429             width += cluster->width();
430             ++cluster;
431         }
432         return std::make_tuple(fEndLine.breakCluster() + 1, 0, width);
433     }
434 
435     // breakCluster points to the end of the line;
436     // It's a soft line break so we need to move lineStart forward skipping all the spaces
437     auto width = fEndLine.widthWithGhostSpaces();
438     auto cluster = fEndLine.breakCluster() + 1;
439     while (cluster < endOfClusters && cluster->isWhitespaceBreak() && !cluster->isTabulation()) {
440         width += cluster->width();
441         ++cluster;
442     }
443 
444     return std::make_tuple(cluster, 0, width);
445 }
446 
447 // calculate heuristics for different variants and select the least bad
448 
449 // calculate the total space required
450 // define the goal for line numbers (max vs space required).
451 // If text could fit, it has substantially larger score compared to nicer wrap with overflow
452 
453 // iterate: select nontrivial candidates with some maximum offset and set the penalty / benefit of variants
454 // goals: 0) fit maximum amount of text
455 //        1) fill lines
456 //        2) make line lengths even
457 //        2.5) define a cost for hyphenation - not done
458 //        3) try to make it fast
459 
460 constexpr int64_t MINIMUM_FILL_RATIO = 75;
461 constexpr int64_t MINIMUM_FILL_RATIO_SQUARED = MINIMUM_FILL_RATIO * MINIMUM_FILL_RATIO;
462 constexpr int64_t GOOD_ENOUGH_LINE_SCORE = 95 * 95;
463 constexpr int64_t UNDERFLOW_SCORE = 100;
464 constexpr float BALANCED_LAST_LINE_MULTIPLIER = 1.4f;
465 constexpr int64_t BEST_LOCAL_SCORE = -1000000;
466 constexpr float  WIDTH_TOLERANCE = 5.f;
467 constexpr int64_t PARAM_2 = 2;
468 constexpr int64_t PARAM_10000 = 10000;
469 
470 // mkay, this makes an assumption that we do the scoring runs in a single thread and holds the variables during
471 // recursion
472 struct TextWrapScorer {
473     TextWrapScorer(SkScalar maxWidth, ParagraphImpl& parent, size_t maxLines)
474         : maxWidth_(maxWidth), currentTarget_(maxWidth), maxLines_(maxLines), parent_(parent)
475     {
476         CalculateCumulativeLen(parent);
477 
478         if (parent_.getLineBreakStrategy() == LineBreakStrategy::BALANCED) {
479             // calculate target width before breaks
480             int64_t targetLines = 1 + cumulativeLen_ / maxWidth_;
481             currentTarget_ = cumulativeLen_ / targetLines;
482         }
483 
484         GenerateBreaks(parent);
485     }
486 
487     void GenerateBreaks(ParagraphImpl& parent)
488     {
489         // we trust that clusters are sorted on parent
490         bool prevWasWhitespace = false;
491         SkScalar currentWidth = 0;
492         size_t currentCount = 0; // in principle currentWidth != 0 should provide same result
493         SkScalar cumulativeLen = 0;
494         for (size_t ix = 0; ix < parent.clusters().size(); ix++) {
495             auto& cluster = parent.clusters()[ix];
496             auto len = cluster.width();
497             cumulativeLen += len;
498             currentWidth += len;
499             currentCount++;
500             if (cluster.isWhitespaceBreak()) {
501                 breaks_.emplace_back(cumulativeLen, Break::BreakType::BREAKTYPE_WHITE_SPACE, prevWasWhitespace);
502                 prevWasWhitespace = true;
503                 currentWidth = 0;
504                 currentCount = 0;
505             } else if (cluster.isHardBreak()) {
506                 breaks_.emplace_back(cumulativeLen, Break::BreakType::BREAKTYPE_HARD, false);
507                 prevWasWhitespace = true;
508                 currentWidth = 0;
509                 currentCount = 0;
510             } else if (cluster.isHyphenBreak()) {
511                 breaks_.emplace_back(cumulativeLen - cluster.width() + cluster.height(),
512                                      Break::BreakType::BREAKTYPE_HYPHEN, false);
513                 breaks_.back().reservedSpace = cluster.height();
514                 prevWasWhitespace = true;
515                 currentWidth = 0;
516                 currentCount = 0;
517             } else if (cluster.isIntraWordBreak()) {
518                 breaks_.emplace_back(cumulativeLen, Break::BreakType::BREAKTYPE_INTRA, false);
519                 prevWasWhitespace = true;
520                 currentWidth = 0;
521                 currentCount = 0;
522             } else if (currentWidth > currentTarget_) {
523                 if (currentCount > 1) {
524                     cumulativeLen -= cluster.width();
525                     ix--;
526                 }
527                 breaks_.emplace_back(cumulativeLen, Break::BreakType::BREAKTYPE_FORCED, false);
528                 prevWasWhitespace = false;
529                 currentWidth = 0;
530                 currentCount = 0;
531             } else {
532                 prevWasWhitespace = false;
533             }
534         }
535     }
536 
537     void CalculateCumulativeLen(ParagraphImpl& parent)
538     {
539         auto startCluster = &parent.cluster(0);
540         auto endCluster = &parent.cluster(0);
541         auto locale = parent.paragraphStyle().getTextStyle().getLocale();
542         for (size_t clusterIx = 0; clusterIx < parent.clusters().size(); clusterIx++) {
543             if (parent.getLineBreakStrategy() == LineBreakStrategy::BALANCED) {
544                 auto& cluster = parent.cluster(clusterIx);
545                 auto len = cluster.width();
546                 cumulativeLen_ += len;
547             }
548             CalculateHyphenPos(clusterIx, startCluster, endCluster, parent, locale);
549         }
550     }
551 
552     void CalculateHyphenPos(size_t clusterIx, Cluster*& startCluster, Cluster*& endCluster, ParagraphImpl& parent,
553                             const SkString& locale)
554     {
555         auto& cluster = parent.cluster(clusterIx);
556         const bool hyphenEnabled = parent.getWordBreakType() == WordBreakType::BREAK_HYPHEN;
557         bool isWhitespace = (cluster.isHardBreak() || cluster.isWhitespaceBreak() || cluster.isTabulation());
558         if (hyphenEnabled && !fPrevWasWhitespace && isWhitespace && endCluster > startCluster) {
559             fPrevWasWhitespace = true;
560             auto results = Hyphenator::getInstance().findBreakPositions(
561                 locale, parent.fText, startCluster->textRange().start, endCluster->textRange().end);
562             CheckHyphenBreak(results, parent, startCluster);
563             if (clusterIx + 1 < parent.clusters().size()) {
564                 startCluster = &cluster + 1;
565             }
566         } else if (!isWhitespace) {
567             fPrevWasWhitespace = false;
568             endCluster = &cluster;
569         } else { //  fix "one character + space and then the actual target"
570             uint32_t i = 1;
571             while (clusterIx + i < parent.clusters().size()) {
572                 if (!parent.cluster(clusterIx + i).isWordBreak()) {
573                     startCluster = &cluster + i;
574                     break;
575                 } else {
576                     i++;
577                 }
578             }
579         }
580     }
581 
582     void CheckHyphenBreak(std::vector<uint8_t> results, ParagraphImpl& parent, Cluster*& startCluster)
583     {
584         size_t prevClusterIx = 0;
585         for (size_t resultIx = 0; resultIx < results.size(); resultIx++) {
586             if (results[resultIx] & 0x1) {
587                 auto clusterPos = parent.clusterIndex(startCluster->textRange().start + resultIx);
588                 if (clusterPos != prevClusterIx) {
589                     parent.cluster(clusterPos).enableHyphenBreak();
590                     prevClusterIx = clusterPos;
591                 }
592             }
593         }
594     }
595 
596     struct RecursiveParam {
597         int64_t targetLines;
598         size_t maxLines;
599         size_t lineNumber;
600         SkScalar begin;
601         SkScalar remainingTextWidth;
602         SkScalar currentMax;
603         size_t breakPos;
604     };
605 
606     void Run() {
607         int64_t targetLines = 1 + cumulativeLen_ / maxWidth_;
608 
609         if (parent_.getLineBreakStrategy() == LineBreakStrategy::BALANCED) {
610             currentTarget_ = cumulativeLen_ / targetLines;
611         }
612 
613         if (targetLines < PARAM_2) {
614             // need to have at least two lines for algo to do anything useful
615             return;
616         }
617         CalculateRecursive(RecursiveParam{
618             targetLines, maxLines_, 0, 0.f, cumulativeLen_,
619         });
620         LOGD("cache_: %{public}zu", cache_.size());
621     }
622 
623     int64_t CalculateRecursive(RecursiveParam param)
624     {
625         if (param.maxLines == 0 || param.remainingTextWidth <= 1.f) {
626             return BEST_LOCAL_SCORE;
627         }
628 
629         // This should come precalculated
630         param.currentMax = maxWidth_ - parent_.detectIndents(param.lineNumber);
631         if (nearlyZero(param.currentMax)) {
632             return BEST_LOCAL_SCORE;
633         }
634 
635         // trim possible spaces at the beginning of line
636         while ((param.lineNumber > 0) && (lastBreakPos_ + 1 < breaks_.size()) &&
637             (breaks_[lastBreakPos_ + 1].subsequentWhitespace)) {
638             param.remainingTextWidth += (param.begin - breaks_[++lastBreakPos_].width);
639             param.begin = breaks_[lastBreakPos_].width;
640         }
641 
642         if (lastBreakPos_ < breaks_.size() && breaks_[lastBreakPos_].type == Break::BreakType::BREAKTYPE_FORCED) {
643             lastBreakPos_++;
644         }
645         param.breakPos = lastBreakPos_;
646 
647         while (param.breakPos < breaks_.size() && breaks_[param.breakPos].width < (param.begin + param.currentMax)) {
648             param.breakPos++;
649         }
650 
651         if (param.breakPos == lastBreakPos_ && param.remainingTextWidth > param.currentMax) {
652             // If we were unable to find a break that matches the criteria, insert new one
653             // This may happen if there is a long word and per line indent for this particular line
654             if (param.breakPos + 1 > breaks_.size()) {
655                 breaks_.emplace_back(param.begin + param.currentMax, Break::BreakType::BREAKTYPE_FORCED, false);
656             } else {
657                 breaks_.insert(breaks_.cbegin() + param.breakPos + 1, Break(param.begin + param.currentMax,
658                     Break::BreakType::BREAKTYPE_FORCED, false));
659             }
660             param.breakPos += BREAK_NUM_TWO;
661         }
662 
663         LOGD("Line %{public}lu about to loop %{public}f, %{public}lu, %{public}lu, max: %{public}f",
664             static_cast<unsigned long>(param.lineNumber), param.begin, static_cast<unsigned long>(param.breakPos),
665             static_cast<unsigned long>(lastBreakPos_), maxWidth_);
666 
667         return FindOptimalSolutionForCurrentLine(param);
668     }
669 
670     std::vector<SkScalar>& GetResult()
671     {
672         return current_;
673     }
674 
675     SkScalar calculateCurrentWidth(RecursiveParam& param, bool looped)
676     {
677         SkScalar newWidth = param.currentMax;
678 
679         if (param.breakPos > 0 && param.begin < breaks_[param.breakPos - 1].width) {
680             newWidth = std::min(breaks_[--param.breakPos].width - param.begin, param.currentMax);
681         }
682 
683         if (looped
684             && ((lastBreakPos_ == param.breakPos)
685                 || (newWidth / param.currentMax * UNDERFLOW_SCORE < MINIMUM_FILL_RATIO))) {
686             LOGD("line %{public}lu breaking %{public}f, %{public}lu, %{public}f/%{public}f",
687                  static_cast<unsigned long>(param.lineNumber), param.begin, static_cast<unsigned long>(param.breakPos),
688                  newWidth, maxWidth_);
689             return 0;
690         }
691 
692         lastBreakPos_ = param.breakPos;
693 
694         return std::min(newWidth, param.remainingTextWidth);
695     }
696 
697     int64_t FindOptimalSolutionForCurrentLine(RecursiveParam& param)
698     {
699         // have this in reversed order to avoid extra insertions
700         std::vector<SkScalar> currentBest;
701         bool looped = false;
702         int64_t score = 0;
703         int64_t overallScore = score;
704         int64_t bestLocalScore = BEST_LOCAL_SCORE;
705         do {
706             // until the given threshold is crossed (minimum line fill rate)
707             // re-break this line, if a result is different, calculate score
708             SkScalar currentWidth = calculateCurrentWidth(param, looped);
709             if (currentWidth == 0) {
710                 break;
711             }
712             Index index { param.lineNumber, param.begin, currentWidth };
713 
714             // check cache
715             const auto& ite = cache_.find(index);
716             if (ite != cache_.cend()) {
717                 cacheHits_++;
718                 current_ = ite->second.widths;
719                 overallScore = ite->second.score;
720                 UpdateSolution(bestLocalScore, overallScore, currentBest);
721                 looped = true;
722                 continue;
723             }
724             SkScalar scoref = std::min(1.f, abs(currentTarget_ - currentWidth) / currentTarget_);
725             score = int64_t((1.f - scoref) * UNDERFLOW_SCORE);
726             score *= score;
727 
728             current_.clear();
729             overallScore = score;
730 
731             // Handle last line
732             if (breaks_[param.breakPos].type == Break::BreakType::BREAKTYPE_HYPHEN) {
733                 auto copy = currentWidth - breaks_[param.breakPos].reservedSpace;
734                 // name is bit confusing as the method enters also recursion
735                 // with hyphen break this never is the last line
736                 if (!HandleLastLine(param, overallScore, copy, score)) {
737                     break;
738                 }
739             } else { // real last line may update currentWidth
740                 if (!HandleLastLine(param, overallScore, currentWidth, score)) {
741                     break;
742                 }
743             }
744             // we have exceeded target number of lines, add some penalty
745             if (param.targetLines < 0) {
746                 overallScore += param.targetLines * PARAM_10000; // MINIMUM_FILL_RATIO;
747             }
748 
749             // We always hold the best possible score of children at this point
750             current_.push_back(currentWidth);
751             cache_[index] = { overallScore, current_ };
752 
753             UpdateSolution(bestLocalScore, overallScore, currentBest);
754             looped = true;
755         } while (score > MINIMUM_FILL_RATIO_SQUARED &&
756             !(param.lineNumber == 0 && bestLocalScore > param.targetLines * GOOD_ENOUGH_LINE_SCORE));
757         current_ = currentBest;
758         return bestLocalScore;
759     }
760 
761     bool HandleLastLine(RecursiveParam& param, int64_t& overallScore, SkScalar& currentWidth, int64_t&score)
762     {
763         // Handle last line
764         if (abs(currentWidth - param.remainingTextWidth) < 1.f) {
765             // this is last line, with high-quality wrapping, relax the score a bit
766             if (parent_.getLineBreakStrategy() == LineBreakStrategy::HIGH_QUALITY) {
767                 overallScore = std::max(MINIMUM_FILL_RATIO, overallScore);
768             } else {
769                 overallScore *= BALANCED_LAST_LINE_MULTIPLIER;
770             }
771 
772             // let's break the loop, under no same condition / fill-rate added rows can result to a better
773             // score.
774             currentWidth = param.currentMax;
775             score = MINIMUM_FILL_RATIO_SQUARED - 1;
776             LOGD("last line %{public}lu reached", static_cast<unsigned long>(param.lineNumber));
777             return true;
778         }
779         if (((param.remainingTextWidth - currentWidth) / maxWidth_) < param.maxLines) {
780             // recursively calculate best score for children
781             overallScore += CalculateRecursive(RecursiveParam{
782                 param.targetLines - 1,
783                 param.maxLines > param.lineNumber ? param.maxLines - param.lineNumber : 0,
784                 param.lineNumber + 1,
785                 param.begin + currentWidth,
786                 param.remainingTextWidth - currentWidth
787             });
788             lastBreakPos_ = param.breakPos; // restore our ix
789             return true;
790         }
791 
792         // the text is not going to fit anyway (anymore), no need to push it
793         return false;
794     }
795 
796     void UpdateSolution(int64_t& bestLocalScore, const int64_t overallScore, std::vector<SkScalar>& currentBest)
797     {
798         if (overallScore > bestLocalScore) {
799             bestLocalScore = overallScore;
800             currentBest = current_;
801         }
802     }
803 
804 private:
805     struct Index {
806         size_t lineNumber { 0 };
807         SkScalar begin { 0 };
808         SkScalar width { 0 };
809         bool operator==(const Index& other) const
810         {
811             return (lineNumber == other.lineNumber && fabs(begin - other.begin) < WIDTH_TOLERANCE &&
812                 fabs(width - other.width) < WIDTH_TOLERANCE);
813         }
814         bool operator<(const Index& other) const
815         {
816             return lineNumber < other.lineNumber ||
817                 (lineNumber == other.lineNumber && other.begin - begin > WIDTH_TOLERANCE) ||
818                 (lineNumber == other.lineNumber && fabs(begin - other.begin) < WIDTH_TOLERANCE &&
819                 other.width - width > WIDTH_TOLERANCE);
820         }
821     };
822 
823     struct Score {
824         int64_t score { 0 };
825         // in reversed order
826         std::vector<SkScalar> widths;
827     };
828 
829     // to be seen if unordered map would be better fit
830     std::map<Index, Score> cache_;
831 
832     SkScalar maxWidth_ { 0 };
833     SkScalar currentTarget_ { 0 };
834     SkScalar cumulativeLen_ { 0 };
835     size_t maxLines_ { 0 };
836     ParagraphImpl& parent_;
837     std::vector<SkScalar> current_;
838 
839     struct Break {
840         enum class BreakType {
841             BREAKTYPE_NONE,
842             BREAKTYPE_HARD,
843             BREAKTYPE_WHITE_SPACE,
844             BREAKTYPE_INTRA,
845             BREAKTYPE_FORCED,
846             BREAKTYPE_HYPHEN
847         };
848         Break(SkScalar w, BreakType t, bool ssws) : width(w), type(t), subsequentWhitespace(ssws) {}
849 
850         SkScalar width { 0.f };
851         BreakType type { BreakType::BREAKTYPE_NONE };
852         bool subsequentWhitespace { false };
853         SkScalar reservedSpace { 0.f };
854     };
855 
856     std::vector<Break> breaks_;
857     size_t lastBreakPos_ { 0 };
858 
859     uint64_t cacheHits_ { 0 };
860 	bool fPrevWasWhitespace{false};
861 };
862 
863 uint64_t TextWrapper::CalculateBestScore(std::vector<SkScalar>& widthOut, SkScalar maxWidth,
864     ParagraphImpl* parent, size_t maxLines) {
865     if (maxLines == 0 || !parent || nearlyZero(maxWidth)) {
866         return -1;
867     }
868 
869     TextWrapScorer* scorer = new TextWrapScorer(maxWidth, *parent, maxLines);
870     scorer->Run();
871     while (scorer && scorer->GetResult().size()) {
872         auto width = scorer->GetResult().back();
873         widthOut.push_back(width);
874         LOGD("width %{public}f", width);
875         scorer->GetResult().pop_back();
876     }
877 
878     delete scorer;
879     return 0;
880 }
881 
882 void TextWrapper::updateMetricsWithPlaceholder(std::vector<Run*>& runs, bool iterateByCluster) {
883     if (!iterateByCluster) {
884         Run* lastRun = nullptr;
885         for (auto& run : runs) {
886             if (run == lastRun) {
887                 continue;
888             }
889             lastRun = run;
890             if (lastRun != nullptr && lastRun->placeholderStyle() != nullptr) {
891                 SkASSERT(lastRun->size() == 1);
892                 // Update the placeholder metrics so we can get the placeholder positions later
893                 // and the line metrics (to make sure the placeholder fits)
894                 lastRun->updateMetrics(&fEndLine.metrics());
895             }
896         }
897         return;
898     }
899     runs.clear();
900     // Deal with placeholder clusters == runs[@size==1]
901     Run* lastRun = nullptr;
902     for (auto cluster = fEndLine.startCluster(); cluster <= fEndLine.endCluster(); ++cluster) {
903         auto r = cluster->runOrNull();
904         if (r == lastRun) {
905             continue;
906         }
907         lastRun = r;
908         if (lastRun != nullptr && lastRun->placeholderStyle() != nullptr) {
909             SkASSERT(lastRun->size() == 1);
910             // Update the placeholder metrics so we can get the placeholder positions later
911             // and the line metrics (to make sure the placeholder fits)
912             lastRun->updateMetrics(&fEndLine.metrics());
913             runs.emplace_back(lastRun);
914         }
915     }
916 }
917 
918 // TODO: refactor the code for line ending (with/without ellipsis)
919 void TextWrapper::breakTextIntoLines(ParagraphImpl* parent,
920                                      SkScalar maxWidth,
921                                      const AddLineToParagraph& addLine) {
922     fHeight = 0;
923     fMinIntrinsicWidth = std::numeric_limits<SkScalar>::min();
924     fMaxIntrinsicWidth = std::numeric_limits<SkScalar>::min();
925 
926     auto span = parent->clusters();
927     if (span.empty()) {
928         return;
929     }
930     auto maxLines = parent->paragraphStyle().getMaxLines();
931     auto align = parent->paragraphStyle().effective_align();
932     auto unlimitedLines = maxLines == std::numeric_limits<size_t>::max();
933     auto endlessLine = !SkScalarIsFinite(maxWidth);
934     auto hasEllipsis = parent->paragraphStyle().ellipsized();
935 
936     auto disableFirstAscent = parent->paragraphStyle().getTextHeightBehavior() & TextHeightBehavior::kDisableFirstAscent;
937     auto disableLastDescent = parent->paragraphStyle().getTextHeightBehavior() & TextHeightBehavior::kDisableLastDescent;
938     bool firstLine = true; // We only interested in fist line if we have to disable the first ascent
939 
940     // Resolve balanced line widths
941     std::vector<SkScalar> balancedWidths;
942 
943     // if word breaking strategy is nontrivial (balanced / optimal), AND word break mode is not BREAK_ALL
944     if (parent->getWordBreakType() != WordBreakType::BREAK_ALL &&
945         parent->getLineBreakStrategy() != LineBreakStrategy::GREEDY) {
946         if (CalculateBestScore(balancedWidths, maxWidth, parent, maxLines) < 0) {
947             // if the line breaking strategy returns a negative score, the algorithm could not fit or break the text
948             // fall back to default, greedy algorithm
949             balancedWidths.clear();
950         }
951         LOGD("Got %{public}lu", static_cast<unsigned long>(balancedWidths.size()));
952     }
953 
954     SkScalar softLineMaxIntrinsicWidth = 0;
955     fEndLine = TextStretch(span.begin(), span.begin(), parent->strutForceHeight());
956     auto end = span.end() - 1;
957     auto start = span.begin();
958     InternalLineMetrics maxRunMetrics;
959     bool needEllipsis = false;
960     SkScalar newWidth = maxWidth;
961     SkScalar noIndentWidth = maxWidth;
962     while (fEndLine.endCluster() != end) {
963         noIndentWidth = maxWidth - parent->detectIndents(fLineNumber - 1);
964         if (maxLines == 1 && parent->paragraphStyle().getEllipsisMod() == EllipsisModal::HEAD) {
965             newWidth = FLT_MAX;
966         } else if (!balancedWidths.empty() && fLineNumber - 1 < balancedWidths.size()) {
967             newWidth = balancedWidths[fLineNumber - 1];
968         } else {
969             newWidth = maxWidth - parent->detectIndents(fLineNumber - 1);
970         }
971         auto lastLine = (hasEllipsis && unlimitedLines) || fLineNumber >= maxLines;
972         needEllipsis = hasEllipsis && !endlessLine && lastLine;
973 
974         this->lookAhead(newWidth, end, parent->getApplyRoundingHack(), parent->getWordBreakType(), needEllipsis);
975 
976         this->moveForward(needEllipsis, parent->getWordBreakType() == WordBreakType::BREAK_ALL);
977         if (fEndLine.endCluster() >= fEndLine.startCluster() || maxLines > 1) {
978             needEllipsis &= fEndLine.endCluster() < end - 1; // Only if we have some text to ellipsize
979         }
980 
981         // Do not trim end spaces on the naturally last line of the left aligned text
982         this->trimEndSpaces(align);
983 
984         // For soft line breaks add to the line all the spaces next to it
985         Cluster* startLine;
986         size_t pos;
987         SkScalar widthWithSpaces;
988         std::tie(startLine, pos, widthWithSpaces) = this->trimStartSpaces(end);
989 
990         if (needEllipsis && !fHardLineBreak) {
991             // This is what we need to do to preserve a space before the ellipsis
992             fEndLine.restoreBreak();
993             widthWithSpaces = fEndLine.widthWithGhostSpaces();
994         } else if (fBrokeLineWithHyphen) {
995             fEndLine.shiftWidth(fEndLine.endCluster()->width());
996         }
997 
998         // If the line is empty with the hard line break, let's take the paragraph font (flutter???)
999         if (fEndLine.metrics().isClean()) {
1000             fEndLine.setMetrics(parent->getEmptyMetrics());
1001         }
1002 
1003         std::vector<Run*> runs;
1004         updateMetricsWithPlaceholder(runs, true);
1005         // update again for some case
1006         // such as :
1007         //      placeholderA(width: 100, height: 100, align: bottom) placeholderB(width: 200, height: 200, align: top)
1008         // without second update: the placeholderA bottom will be set 0, and placeholderB bottom will be set 100
1009         // so the fEndline bottom will be set 100, is not equal placeholderA bottom
1010         updateMetricsWithPlaceholder(runs, false);
1011         // Before we update the line metrics with struts,
1012         // let's save it for GetRectsForRange(RectHeightStyle::kMax)
1013         maxRunMetrics = fEndLine.metrics();
1014         maxRunMetrics.fForceStrut = false;
1015 
1016         // TODO: keep start/end/break info for text and runs but in a better way that below
1017         TextRange textExcludingSpaces(fEndLine.startCluster()->textRange().start, fEndLine.endCluster()->textRange().end);
1018         TextRange text(fEndLine.startCluster()->textRange().start, fEndLine.breakCluster()->textRange().start);
1019         TextRange textIncludingNewlines(fEndLine.startCluster()->textRange().start, startLine->textRange().start);
1020         if (startLine == end) {
1021             textIncludingNewlines.end = parent->text().size();
1022             text.end = parent->text().size();
1023         }
1024         ClusterRange clusters(fEndLine.startCluster() - start, fEndLine.endCluster() - start + 1);
1025         ClusterRange clustersWithGhosts(fEndLine.startCluster() - start, startLine - start);
1026 
1027         if (disableFirstAscent && firstLine) {
1028             fEndLine.metrics().fAscent = fEndLine.metrics().fRawAscent;
1029         }
1030         if (disableLastDescent && (lastLine || (startLine == end && !fHardLineBreak))) {
1031             fEndLine.metrics().fDescent = fEndLine.metrics().fRawDescent;
1032         }
1033 
1034         if (parent->strutEnabled()) {
1035             // Make sure font metrics are not less than the strut
1036             parent->strutMetrics().updateLineMetrics(fEndLine.metrics());
1037         }
1038 
1039         SkScalar lineHeight = fEndLine.metrics().height();
1040         if (fHardLineBreak && !lastLine && parent->paragraphStyle().getParagraphSpacing() > 0) {
1041             lineHeight += parent->paragraphStyle().getParagraphSpacing();
1042         }
1043         firstLine = false;
1044 
1045         if (fEndLine.empty()) {
1046             // Correct text and clusters (make it empty for an empty line)
1047             textExcludingSpaces.end = textExcludingSpaces.start;
1048             clusters.end = clusters.start;
1049         }
1050 
1051         // In case of a force wrapping we don't have a break cluster and have to use the end cluster
1052         text.end = std::max(text.end, textExcludingSpaces.end);
1053 
1054         if (parent->paragraphStyle().getEllipsisMod() == EllipsisModal::HEAD && hasEllipsis) {
1055             needEllipsis = maxLines <= 1;
1056             if (needEllipsis) {
1057                 fHardLineBreak = false;
1058             }
1059         }
1060 
1061         SkScalar offsetX = parent->detectIndents(fLineNumber - 1);
1062         addLine(textExcludingSpaces,
1063                 text,
1064                 textIncludingNewlines, clusters, clustersWithGhosts, widthWithSpaces,
1065                 fEndLine.startPos(),
1066                 fEndLine.endPos(),
1067                 SkVector::Make(offsetX, fHeight),
1068                 SkVector::Make(fEndLine.width(), lineHeight),
1069                 fEndLine.metrics(),
1070                 needEllipsis,
1071                 offsetX,
1072                 noIndentWidth);
1073 
1074         softLineMaxIntrinsicWidth += widthWithSpaces;
1075 
1076         fMaxIntrinsicWidth = std::max(fMaxIntrinsicWidth, softLineMaxIntrinsicWidth);
1077         if (fHardLineBreak) {
1078             softLineMaxIntrinsicWidth = 0;
1079         }
1080         // Start a new line
1081         fHeight += lineHeight;
1082         if (!fHardLineBreak || startLine != end) {
1083             fEndLine.clean();
1084         }
1085         fEndLine.startFrom(startLine, pos);
1086         parent->fMaxWidthWithTrailingSpaces = std::max(parent->fMaxWidthWithTrailingSpaces, widthWithSpaces);
1087 
1088         if (hasEllipsis && unlimitedLines) {
1089             // There is one case when we need an ellipsis on a separate line
1090             // after a line break when width is infinite
1091             if (!fHardLineBreak) {
1092                 break;
1093             }
1094         } else if (lastLine) {
1095             // There is nothing more to draw
1096             fHardLineBreak = false;
1097             break;
1098         }
1099 
1100         ++fLineNumber;
1101     }
1102     if (parent->paragraphStyle().getIsEndAddParagraphSpacing() &&
1103         parent->paragraphStyle().getParagraphSpacing() > 0) {
1104         fHeight += parent->paragraphStyle().getParagraphSpacing();
1105     }
1106 
1107     // We finished formatting the text but we need to scan the rest for some numbers
1108     // TODO: make it a case of a normal flow
1109     if (fEndLine.endCluster() != nullptr) {
1110         auto lastWordLength = 0.0f;
1111         auto cluster = fEndLine.endCluster();
1112         while (cluster != end || cluster->endPos() < end->endPos()) {
1113             fExceededMaxLines = true;
1114             if (cluster->isHardBreak()) {
1115                 // Hard line break ends the word and the line
1116                 fMaxIntrinsicWidth = std::max(fMaxIntrinsicWidth, softLineMaxIntrinsicWidth);
1117                 softLineMaxIntrinsicWidth = 0;
1118                 fMinIntrinsicWidth = std::max(fMinIntrinsicWidth, lastWordLength);
1119                 lastWordLength = 0;
1120             } else if (cluster->isWhitespaceBreak()) {
1121                 // Whitespaces end the word
1122                 softLineMaxIntrinsicWidth += cluster->width();
1123                 fMinIntrinsicWidth = std::max(fMinIntrinsicWidth, lastWordLength);
1124                 lastWordLength = 0;
1125             } else if (cluster->run().isPlaceholder()) {
1126                 // Placeholder ends the previous word and creates a separate one
1127                 fMinIntrinsicWidth = std::max(fMinIntrinsicWidth, lastWordLength);
1128                 // Placeholder width now counts in fMinIntrinsicWidth
1129                 softLineMaxIntrinsicWidth += cluster->width();
1130                 fMinIntrinsicWidth = std::max(fMinIntrinsicWidth, cluster->width());
1131                 lastWordLength = 0;
1132             } else {
1133                 // Nothing out of ordinary - just add this cluster to the word and to the line
1134                 softLineMaxIntrinsicWidth += cluster->width();
1135                 lastWordLength += cluster->width();
1136             }
1137             ++cluster;
1138         }
1139         fMinIntrinsicWidth = std::max(fMinIntrinsicWidth, lastWordLength);
1140         fMaxIntrinsicWidth = std::max(fMaxIntrinsicWidth, softLineMaxIntrinsicWidth);
1141 
1142         if (parent->lines().empty()) {
1143             // In case we could not place even a single cluster on the line
1144             if (disableFirstAscent) {
1145                 fEndLine.metrics().fAscent = fEndLine.metrics().fRawAscent;
1146             }
1147             if (disableLastDescent && !fHardLineBreak) {
1148                 fEndLine.metrics().fDescent = fEndLine.metrics().fRawDescent;
1149             }
1150             fHeight = std::max(fHeight, fEndLine.metrics().height());
1151         }
1152     }
1153 
1154     if (fHardLineBreak) {
1155         if (disableLastDescent) {
1156             fEndLine.metrics().fDescent = fEndLine.metrics().fRawDescent;
1157         }
1158 
1159         // Last character is a line break
1160         if (parent->strutEnabled()) {
1161             // Make sure font metrics are not less than the strut
1162             parent->strutMetrics().updateLineMetrics(fEndLine.metrics());
1163         }
1164 
1165         ClusterRange clusters(fEndLine.breakCluster() - start, fEndLine.endCluster() - start);
1166         addLine(fEndLine.breakCluster()->textRange(),
1167                 fEndLine.breakCluster()->textRange(),
1168                 fEndLine.endCluster()->textRange(),
1169                 clusters,
1170                 clusters,
1171                 0,
1172                 0,
1173                 0,
1174                 SkVector::Make(0, fHeight),
1175                 SkVector::Make(0, fEndLine.metrics().height()),
1176                 fEndLine.metrics(),
1177                 needEllipsis,
1178                 parent->detectIndents(fLineNumber - 1),
1179                 noIndentWidth);
1180         fHeight += fEndLine.metrics().height();
1181         parent->lines().back().setMaxRunMetrics(maxRunMetrics);
1182     }
1183 
1184     if (parent->lines().empty()) {
1185         return;
1186     }
1187     // Correct line metric styles for the first and for the last lines if needed
1188     if (disableFirstAscent) {
1189         parent->lines().front().setAscentStyle(LineMetricStyle::Typographic);
1190     }
1191     if (disableLastDescent) {
1192         parent->lines().back().setDescentStyle(LineMetricStyle::Typographic);
1193     }
1194 }
1195 #else
1196 // Since we allow cluster clipping when they don't fit
1197 // we have to work with stretches - parts of clusters
1198 void TextWrapper::lookAhead(SkScalar maxWidth, Cluster* endOfClusters, bool applyRoundingHack) {
1199     reset();
1200     fEndLine.metrics().clean();
1201     fWords.startFrom(fEndLine.startCluster(), fEndLine.startPos());
1202     fClusters.startFrom(fEndLine.startCluster(), fEndLine.startPos());
1203     fClip.startFrom(fEndLine.startCluster(), fEndLine.startPos());
1204     LineBreakerWithLittleRounding breaker(maxWidth, applyRoundingHack);
1205     Cluster* nextNonBreakingSpace = nullptr;
1206     for (auto cluster = fEndLine.endCluster(); cluster < endOfClusters; ++cluster) {
1207         if (cluster->isHardBreak()) {
1208         } else if (
1209                 SkScalar width = fWords.width() + fClusters.width() + cluster->width();
1210                 breaker.breakLine(width)) {
1211             if (cluster->isWhitespaceBreak()) {
1212                 // It's the end of the word
1213                 fClusters.extend(cluster);
1214                 fMinIntrinsicWidth = std::max(fMinIntrinsicWidth, this->getClustersTrimmedWidth());
1215                 fWords.extend(fClusters);
1216                 continue;
1217             } else if (cluster->run().isPlaceholder()) {
1218                 if (!fClusters.empty()) {
1219                     // Placeholder ends the previous word
1220                     fMinIntrinsicWidth = std::max(fMinIntrinsicWidth, this->getClustersTrimmedWidth());
1221                     fWords.extend(fClusters);
1222                 }
1223                 if (cluster->width() > maxWidth && fWords.empty()) {
1224                     // Placeholder is the only text and it's longer than the line;
1225                     // it does not count in fMinIntrinsicWidth
1226                     fClusters.extend(cluster);
1227                     fTooLongCluster = true;
1228                     fTooLongWord = true;
1229                 } else {
1230                     // Placeholder does not fit the line; it will be considered again on the next line
1231                 }
1232                 break;
1233             }
1234             // Walk further to see if there is a too long word, cluster or glyph
1235             SkScalar nextWordLength = fClusters.width();
1236             SkScalar nextShortWordLength = nextWordLength;
1237             for (auto further = cluster; further != endOfClusters; ++further) {
1238                 if (further->isSoftBreak() || further->isHardBreak() || further->isWhitespaceBreak()) {
1239                     break;
1240                 }
1241                 if (further->run().isPlaceholder()) {
1242                   // Placeholder ends the word
1243                   break;
1244                 }
1245                 if (nextWordLength > 0 && nextWordLength <= maxWidth && further->isIntraWordBreak()) {
1246                     // The cluster is spaces but not the end of the word in a normal sense
1247                     nextNonBreakingSpace = further;
1248                     nextShortWordLength = nextWordLength;
1249                 }
1250                 if (maxWidth == 0) {
1251                     // This is a tricky flutter case: layout(width:0) places 1 cluster on each line
1252                     nextWordLength = std::max(nextWordLength, further->width());
1253                 } else {
1254                     nextWordLength += further->width();
1255                 }
1256             }
1257             if (nextWordLength > maxWidth) {
1258                 if (nextNonBreakingSpace != nullptr) {
1259                     // We only get here if the non-breaking space improves our situation
1260                     // (allows us to break the text to fit the word)
1261                     if (SkScalar shortLength = fWords.width() + nextShortWordLength;
1262                         !breaker.breakLine(shortLength)) {
1263                         // We can add the short word to the existing line
1264                         fClusters = TextStretch(fClusters.startCluster(), nextNonBreakingSpace,
1265                             fClusters.metrics().getForceStrut());
1266                         fMinIntrinsicWidth = std::max(fMinIntrinsicWidth, nextShortWordLength);
1267                         fWords.extend(fClusters);
1268                     } else {
1269                         // We can place the short word on the next line
1270                         fClusters.clean();
1271                     }
1272                     // Either way we are not in "word is too long" situation anymore
1273                     break;
1274                 }
1275                 // If the word is too long we can break it right now and hope it's enough
1276                 fMinIntrinsicWidth = std::max(fMinIntrinsicWidth, nextWordLength);
1277                 if (fClusters.endPos() - fClusters.startPos() > 1 ||
1278                     fWords.empty()) {
1279                     fTooLongWord = true;
1280                 } else {
1281                     // Even if the word is too long there is a very little space on this line.
1282                     // let's deal with it on the next line.
1283                 }
1284             }
1285             if (cluster->width() > maxWidth) {
1286                 fClusters.extend(cluster);
1287                 fTooLongCluster = true;
1288                 fTooLongWord = true;
1289             }
1290             break;
1291         }
1292         if (cluster->run().isPlaceholder()) {
1293             if (!fClusters.empty()) {
1294                 // Placeholder ends the previous word (placeholders are ignored in trimming)
1295                 fMinIntrinsicWidth = std::max(fMinIntrinsicWidth, getClustersTrimmedWidth());
1296                 fWords.extend(fClusters);
1297             }
1298             // Placeholder is separate word and its width now is counted in minIntrinsicWidth
1299             fMinIntrinsicWidth = std::max(fMinIntrinsicWidth, cluster->width());
1300             fWords.extend(cluster);
1301         } else {
1302             fClusters.extend(cluster);
1303             // Keep adding clusters/words
1304             if (fClusters.endOfWord()) {
1305                 fMinIntrinsicWidth = std::max(fMinIntrinsicWidth, getClustersTrimmedWidth());
1306                 fWords.extend(fClusters);
1307             }
1308         }
1309         if ((fHardLineBreak = cluster->isHardBreak())) {
1310             // Stop at the hard line break
1311             break;
1312         }
1313     }
1314 }
1315 void TextWrapper::moveForward(bool hasEllipsis) {
1316     // We normally break lines by words.
1317     // The only way we may go to clusters is if the word is too long or
1318     // it's the first word and it has an ellipsis attached to it.
1319     // If nothing fits we show the clipping.
1320     if (!fWords.empty()) {
1321         fEndLine.extend(fWords);
1322 #ifdef SK_IGNORE_SKPARAGRAPH_ELLIPSIS_FIX
1323         if (!fTooLongWord || hasEllipsis) { // Ellipsis added to a word
1324 #else
1325         if (!fTooLongWord && !hasEllipsis) { // Ellipsis added to a grapheme
1326 #endif
1327             return;
1328         }
1329     }
1330     if (!fClusters.empty()) {
1331         fEndLine.extend(fClusters);
1332         if (!fTooLongCluster) {
1333             return;
1334         }
1335     }
1336     if (!fClip.empty()) {
1337         // Flutter: forget the clipped cluster but keep the metrics
1338         fEndLine.metrics().add(fClip.metrics());
1339     }
1340 }
1341 // Special case for start/end cluster since they can be clipped
1342 void TextWrapper::trimEndSpaces(TextAlign align) {
1343     // Remember the breaking position
1344     fEndLine.saveBreak();
1345     // Skip all space cluster at the end
1346     for (auto cluster = fEndLine.endCluster();
1347          cluster >= fEndLine.startCluster() && cluster->isWhitespaceBreak();
1348          --cluster) {
1349         fEndLine.trim(cluster);
1350     }
1351     fEndLine.trim();
1352 }
1353 SkScalar TextWrapper::getClustersTrimmedWidth() {
1354     // Move the end of the line to the left
1355     SkScalar width = 0;
1356     bool trailingSpaces = true;
1357     for (auto cluster = fClusters.endCluster(); cluster >= fClusters.startCluster(); --cluster) {
1358         if (cluster->run().isPlaceholder()) {
1359             continue;
1360         }
1361         if (trailingSpaces) {
1362             if (!cluster->isWhitespaceBreak()) {
1363                 width += cluster->trimmedWidth(cluster->endPos());
1364                 trailingSpaces = false;
1365             }
1366             continue;
1367         }
1368         width += cluster->width();
1369     }
1370     return width;
1371 }
1372 // Trim the beginning spaces in case of soft line break
1373 std::tuple<Cluster*, size_t, SkScalar> TextWrapper::trimStartSpaces(Cluster* endOfClusters) {
1374     if (fHardLineBreak) {
1375         // End of line is always end of cluster, but need to skip \n
1376         auto width = fEndLine.width();
1377         auto cluster = fEndLine.endCluster() + 1;
1378         while (cluster < fEndLine.breakCluster() && cluster->isWhitespaceBreak())  {
1379             width += cluster->width();
1380             ++cluster;
1381         }
1382         return std::make_tuple(fEndLine.breakCluster() + 1, 0, width);
1383     }
1384     // breakCluster points to the end of the line;
1385     // It's a soft line break so we need to move lineStart forward skipping all the spaces
1386     auto width = fEndLine.widthWithGhostSpaces();
1387     auto cluster = fEndLine.breakCluster() + 1;
1388     while (cluster < endOfClusters && cluster->isWhitespaceBreak()) {
1389         width += cluster->width();
1390         ++cluster;
1391     }
1392     if (fEndLine.breakCluster()->isWhitespaceBreak() && fEndLine.breakCluster() < endOfClusters) {
1393         // In case of a soft line break by the whitespace
1394         // fBreak should point to the beginning of the next line
1395         // (it only matters when there are trailing spaces)
1396         fEndLine.shiftBreak();
1397     }
1398     return std::make_tuple(cluster, 0, width);
1399 }
1400 void TextWrapper::breakTextIntoLines(ParagraphImpl* parent,
1401                                      SkScalar maxWidth,
1402                                      const AddLineToParagraph& addLine) {
1403     fHeight = 0;
1404     fMinIntrinsicWidth = std::numeric_limits<SkScalar>::min();
1405     fMaxIntrinsicWidth = std::numeric_limits<SkScalar>::min();
1406     auto span = parent->clusters();
1407     if (span.empty()) {
1408         return;
1409     }
1410     auto maxLines = parent->paragraphStyle().getMaxLines();
1411     auto align = parent->paragraphStyle().effective_align();
1412     auto unlimitedLines = maxLines == std::numeric_limits<size_t>::max();
1413     auto endlessLine = !SkScalarIsFinite(maxWidth);
1414     auto hasEllipsis = parent->paragraphStyle().ellipsized();
1415     auto disableFirstAscent = parent->paragraphStyle().getTextHeightBehavior() & TextHeightBehavior::kDisableFirstAscent;
1416     auto disableLastDescent = parent->paragraphStyle().getTextHeightBehavior() & TextHeightBehavior::kDisableLastDescent;
1417     bool firstLine = true; // We only interested in fist line if we have to disable the first ascent
1418     SkScalar softLineMaxIntrinsicWidth = 0;
1419     fEndLine = TextStretch(span.begin(), span.begin(), parent->strutForceHeight());
1420     auto end = span.end() - 1;
1421     auto start = span.begin();
1422     InternalLineMetrics maxRunMetrics;
1423     bool needEllipsis = false;
1424     while (fEndLine.endCluster() != end) {
1425         this->lookAhead(maxWidth, end, parent->getApplyRoundingHack());
1426         auto lastLine = (hasEllipsis && unlimitedLines) || fLineNumber >= maxLines;
1427         needEllipsis = hasEllipsis && !endlessLine && lastLine;
1428         this->moveForward(needEllipsis);
1429         needEllipsis &= fEndLine.endCluster() < end - 1; // Only if we have some text to ellipsize
1430         // Do not trim end spaces on the naturally last line of the left aligned text
1431         this->trimEndSpaces(align);
1432         // For soft line breaks add to the line all the spaces next to it
1433         Cluster* startLine;
1434         size_t pos;
1435         SkScalar widthWithSpaces;
1436         std::tie(startLine, pos, widthWithSpaces) = this->trimStartSpaces(end);
1437         if (needEllipsis && !fHardLineBreak) {
1438             // This is what we need to do to preserve a space before the ellipsis
1439             fEndLine.restoreBreak();
1440             widthWithSpaces = fEndLine.widthWithGhostSpaces();
1441         }
1442         // If the line is empty with the hard line break, let's take the paragraph font (flutter???)
1443         if (fEndLine.metrics().isClean()) {
1444             fEndLine.setMetrics(parent->getEmptyMetrics());
1445         }
1446         // Deal with placeholder clusters == runs[@size==1]
1447         Run* lastRun = nullptr;
1448         for (auto cluster = fEndLine.startCluster(); cluster <= fEndLine.endCluster(); ++cluster) {
1449             auto r = cluster->runOrNull();
1450             if (r == lastRun) {
1451                 continue;
1452             }
1453             lastRun = r;
1454             if (lastRun->placeholderStyle() != nullptr) {
1455                 SkASSERT(lastRun->size() == 1);
1456                 // Update the placeholder metrics so we can get the placeholder positions later
1457                 // and the line metrics (to make sure the placeholder fits)
1458                 lastRun->updateMetrics(&fEndLine.metrics());
1459             }
1460         }
1461         // Before we update the line metrics with struts,
1462         // let's save it for GetRectsForRange(RectHeightStyle::kMax)
1463         maxRunMetrics = fEndLine.metrics();
1464         maxRunMetrics.fForceStrut = false;
1465         TextRange textExcludingSpaces(fEndLine.startCluster()->textRange().start, fEndLine.endCluster()->textRange().end);
1466         TextRange text(fEndLine.startCluster()->textRange().start, fEndLine.breakCluster()->textRange().start);
1467         TextRange textIncludingNewlines(fEndLine.startCluster()->textRange().start, startLine->textRange().start);
1468         if (startLine == end) {
1469             textIncludingNewlines.end = parent->text().size();
1470             text.end = parent->text().size();
1471         }
1472         ClusterRange clusters(fEndLine.startCluster() - start, fEndLine.endCluster() - start + 1);
1473         ClusterRange clustersWithGhosts(fEndLine.startCluster() - start, startLine - start);
1474         if (disableFirstAscent && firstLine) {
1475             fEndLine.metrics().fAscent = fEndLine.metrics().fRawAscent;
1476         }
1477         if (disableLastDescent && (lastLine || (startLine == end && !fHardLineBreak ))) {
1478             fEndLine.metrics().fDescent = fEndLine.metrics().fRawDescent;
1479         }
1480         if (parent->strutEnabled()) {
1481             // Make sure font metrics are not less than the strut
1482             parent->strutMetrics().updateLineMetrics(fEndLine.metrics());
1483         }
1484         SkScalar lineHeight = fEndLine.metrics().height();
1485         firstLine = false;
1486         if (fEndLine.empty()) {
1487             // Correct text and clusters (make it empty for an empty line)
1488             textExcludingSpaces.end = textExcludingSpaces.start;
1489             clusters.end = clusters.start;
1490         }
1491         // In case of a force wrapping we don't have a break cluster and have to use the end cluster
1492         text.end = std::max(text.end, textExcludingSpaces.end);
1493         addLine(textExcludingSpaces,
1494                 text,
1495                 textIncludingNewlines, clusters, clustersWithGhosts, widthWithSpaces,
1496                 fEndLine.startPos(),
1497                 fEndLine.endPos(),
1498                 SkVector::Make(0, fHeight),
1499                 SkVector::Make(fEndLine.width(), lineHeight),
1500                 fEndLine.metrics(),
1501                 needEllipsis && !fHardLineBreak);
1502         softLineMaxIntrinsicWidth += widthWithSpaces;
1503         fMaxIntrinsicWidth = std::max(fMaxIntrinsicWidth, softLineMaxIntrinsicWidth);
1504         if (fHardLineBreak) {
1505             softLineMaxIntrinsicWidth = 0;
1506         }
1507         // Start a new line
1508         fHeight += lineHeight;
1509         if (!fHardLineBreak || startLine != end) {
1510             fEndLine.clean();
1511         }
1512         fEndLine.startFrom(startLine, pos);
1513         parent->fMaxWidthWithTrailingSpaces = std::max(parent->fMaxWidthWithTrailingSpaces, widthWithSpaces);
1514         if (hasEllipsis && unlimitedLines) {
1515             // There is one case when we need an ellipsis on a separate line
1516             // after a line break when width is infinite
1517             if (!fHardLineBreak) {
1518                 break;
1519             }
1520         } else if (lastLine) {
1521             // There is nothing more to draw
1522             fHardLineBreak = false;
1523             break;
1524         }
1525         ++fLineNumber;
1526     }
1527     // We finished formatting the text but we need to scan the rest for some numbers
1528     if (fEndLine.endCluster() != nullptr) {
1529         auto lastWordLength = 0.0f;
1530         auto cluster = fEndLine.endCluster();
1531         while (cluster != end || cluster->endPos() < end->endPos()) {
1532             fExceededMaxLines = true;
1533             if (cluster->isHardBreak()) {
1534                 // Hard line break ends the word and the line
1535                 fMaxIntrinsicWidth = std::max(fMaxIntrinsicWidth, softLineMaxIntrinsicWidth);
1536                 softLineMaxIntrinsicWidth = 0;
1537                 fMinIntrinsicWidth = std::max(fMinIntrinsicWidth, lastWordLength);
1538                 lastWordLength = 0;
1539             } else if (cluster->isWhitespaceBreak()) {
1540                 // Whitespaces end the word
1541                 softLineMaxIntrinsicWidth += cluster->width();
1542                 fMinIntrinsicWidth = std::max(fMinIntrinsicWidth, lastWordLength);
1543                 lastWordLength = 0;
1544             } else if (cluster->run().isPlaceholder()) {
1545                 // Placeholder ends the previous word and creates a separate one
1546                 fMinIntrinsicWidth = std::max(fMinIntrinsicWidth, lastWordLength);
1547                 // Placeholder width now counts in fMinIntrinsicWidth
1548                 softLineMaxIntrinsicWidth += cluster->width();
1549                 fMinIntrinsicWidth = std::max(fMinIntrinsicWidth, cluster->width());
1550                 lastWordLength = 0;
1551             } else {
1552                 // Nothing out of ordinary - just add this cluster to the word and to the line
1553                 softLineMaxIntrinsicWidth += cluster->width();
1554                 lastWordLength += cluster->width();
1555             }
1556             ++cluster;
1557         }
1558         fMinIntrinsicWidth = std::max(fMinIntrinsicWidth, lastWordLength);
1559         fMaxIntrinsicWidth = std::max(fMaxIntrinsicWidth, softLineMaxIntrinsicWidth);
1560         if (parent->lines().empty()) {
1561             // In case we could not place even a single cluster on the line
1562             if (disableFirstAscent) {
1563                 fEndLine.metrics().fAscent = fEndLine.metrics().fRawAscent;
1564             }
1565             if (disableLastDescent && !fHardLineBreak) {
1566                 fEndLine.metrics().fDescent = fEndLine.metrics().fRawDescent;
1567             }
1568             fHeight = std::max(fHeight, fEndLine.metrics().height());
1569         }
1570     }
1571     if (fHardLineBreak) {
1572         if (disableLastDescent) {
1573             fEndLine.metrics().fDescent = fEndLine.metrics().fRawDescent;
1574         }
1575         // Last character is a line break
1576         if (parent->strutEnabled()) {
1577             // Make sure font metrics are not less than the strut
1578             parent->strutMetrics().updateLineMetrics(fEndLine.metrics());
1579         }
1580         ClusterRange clusters(fEndLine.breakCluster() - start, fEndLine.endCluster() - start);
1581         addLine(fEndLine.breakCluster()->textRange(),
1582                 fEndLine.breakCluster()->textRange(),
1583                 fEndLine.endCluster()->textRange(),
1584                 clusters,
1585                 clusters,
1586                 0,
1587                 0,
1588                 0,
1589                 SkVector::Make(0, fHeight),
1590                 SkVector::Make(0, fEndLine.metrics().height()),
1591                 fEndLine.metrics(),
1592                 needEllipsis);
1593         fHeight += fEndLine.metrics().height();
1594         parent->lines().back().setMaxRunMetrics(maxRunMetrics);
1595     }
1596     if (parent->lines().empty()) {
1597         return;
1598     }
1599     // Correct line metric styles for the first and for the last lines if needed
1600     if (disableFirstAscent) {
1601         parent->lines().front().setAscentStyle(LineMetricStyle::Typographic);
1602     }
1603     if (disableLastDescent) {
1604         parent->lines().back().setDescentStyle(LineMetricStyle::Typographic);
1605     }
1606 }
1607 #endif
1608 }  // namespace textlayout
1609 }  // namespace skia
1610