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