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