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