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