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