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