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