1 // Copyright 2019 Google LLC.
2 #include "modules/skparagraph/src/ParagraphImpl.h"
3 #include "modules/skparagraph/src/TextWrapper.h"
4
5 #ifdef OHOS_SUPPORT
6 #include <cstdint>
7 #include <iterator>
8 #include <limits>
9 #include <memory>
10 #include <numeric>
11
12 #include "include/DartTypes.h"
13 #include "log.h"
14 #include "modules/skparagraph/include/Hyphenator.h"
15 #include "modules/skparagraph/src/TextTabAlign.h"
16 #include "TextParameter.h"
17 #endif
18
19 namespace skia {
20 namespace textlayout {
21
22 namespace {
23 const size_t BREAK_NUM_TWO = 2;
24
25 struct LineBreakerWithLittleRounding {
LineBreakerWithLittleRoundingskia::textlayout::__anone73489940111::LineBreakerWithLittleRounding26 LineBreakerWithLittleRounding(SkScalar maxWidth, bool applyRoundingHack)
27 : fLower(maxWidth - 0.25f)
28 , fMaxWidth(maxWidth)
29 , fUpper(maxWidth + 0.25f)
30 , fApplyRoundingHack(applyRoundingHack) {}
31
breakLineskia::textlayout::__anone73489940111::LineBreakerWithLittleRounding32 bool breakLine(SkScalar width) const {
33 if (width < fLower) {
34 return false;
35 } else if (width > fUpper) {
36 return true;
37 }
38
39 auto val = std::fabs(width);
40 SkScalar roundedWidth;
41 if (fApplyRoundingHack) {
42 if (val < 10000) {
43 roundedWidth = SkScalarRoundToScalar(width * 100) * (1.0f/100);
44 } else if (val < 100000) {
45 roundedWidth = SkScalarRoundToScalar(width * 10) * (1.0f/10);
46 } else {
47 roundedWidth = SkScalarFloorToScalar(width);
48 }
49 } else {
50 if (val < 10000) {
51 roundedWidth = SkScalarFloorToScalar(width * 100) * (1.0f/100);
52 } else if (val < 100000) {
53 roundedWidth = SkScalarFloorToScalar(width * 10) * (1.0f/10);
54 } else {
55 roundedWidth = SkScalarFloorToScalar(width);
56 }
57 }
58 return roundedWidth > fMaxWidth;
59 }
60
61 const SkScalar fLower, fMaxWidth, fUpper;
62 const bool fApplyRoundingHack;
63 };
64 } // namespace
65
66 #ifdef OHOS_SUPPORT
matchHyphenResult(const std::vector<uint8_t> & result,ParagraphImpl * owner,size_t & pos,SkScalar maxWidth,SkScalar len)67 void TextWrapper::matchHyphenResult(const std::vector<uint8_t>& result, ParagraphImpl* owner, size_t& pos,
68 SkScalar maxWidth, SkScalar len)
69 {
70 auto startPos = pos;
71 size_t ix = 0;
72
73 // Result array may have more code points than we have visible clusters
74 int32_t prevIx = -1;
75 // cumulatively iterate width vs breaks
76 for (const auto& breakPos : result) {
77 int32_t clusterIx = static_cast<int32_t>(owner->fClustersIndexFromCodeUnit[startPos + ix]);
78 if (clusterIx == prevIx) {
79 ++ix;
80 continue;
81 }
82 prevIx = clusterIx;
83 TEXT_LOGD("hyphen break width:%{public}f / %{public}f : %{public}f", len, maxWidth,
84 owner->cluster(clusterIx).width());
85 len += owner->cluster(clusterIx).width();
86 auto shouldBreak = (len > maxWidth);
87 if (breakPos & 0x1) {
88 // we need to break after previous char, but the result needs to be mapped
89 pos = startPos + ix;
90 }
91 ++ix;
92 if (shouldBreak) {
93 break;
94 }
95 }
96 }
97
tryBreakWord(Cluster * startCluster,Cluster * endOfClusters,SkScalar widthBeforeCluster,SkScalar maxWidth)98 size_t TextWrapper::tryBreakWord(Cluster *startCluster, Cluster *endOfClusters,
99 SkScalar widthBeforeCluster, SkScalar maxWidth)
100 {
101 auto startPos = startCluster->textRange().start;
102 auto endPos = startPos;
103 auto owner = startCluster->getOwner();
104 for (auto next = startCluster + 1; next != endOfClusters; next++) {
105 // find the end boundary of current word (hard/soft/whitespace break)
106 if (next->isWhitespaceBreak() || next->isHardBreak()) {
107 break;
108 } else {
109 endPos = next->textRange().end;
110 }
111 }
112
113 // Using end position cluster height as an estimate for reserved hyphen width
114 // hyphen may not be shaped at this point. End pos may be non-drawable, so prefer
115 // previous glyph before break
116 auto mappedEnd = owner->fClustersIndexFromCodeUnit[endPos];
117 auto len = widthBeforeCluster + owner->cluster(mappedEnd > 0 ? mappedEnd - 1 : 0).height();
118 // ToDo: We can stop here based on hyhpenation frequency setting
119 // but there is no need to proceed with hyphenation if we don't have space for even hyphen
120 if (std::isnan(len) || len >= maxWidth) {
121 return startCluster->textRange().start;
122 }
123
124 auto locale = owner->paragraphStyle().getTextStyle().getLocale();
125 auto result = Hyphenator::getInstance().findBreakPositions(locale, owner->fText, startPos, endPos);
126 endPos = startPos;
127 matchHyphenResult(result, owner, endPos, maxWidth, len);
128
129 return endPos;
130 }
131
lookAheadByHyphen(Cluster * endOfClusters,SkScalar widthBeforeCluster,SkScalar maxWidth)132 bool TextWrapper::lookAheadByHyphen(Cluster* endOfClusters, SkScalar widthBeforeCluster, SkScalar maxWidth)
133 {
134 auto startCluster = fClusters.startCluster();
135 while (startCluster != endOfClusters && startCluster->isWhitespaceBreak()) {
136 ++startCluster;
137 }
138 if (startCluster == endOfClusters) {
139 return false;
140 }
141 auto endPos = tryBreakWord(startCluster, endOfClusters, widthBeforeCluster - fClusters.width(), maxWidth);
142 // if break position found, set fClusters and fWords accrodingly and break
143 if (endPos > startCluster->textRange().start) {
144 // need to break before the mapped end cluster
145 auto owner = startCluster->getOwner();
146 fClusters = TextStretch(startCluster, &owner->cluster(owner->fClustersIndexFromCodeUnit[endPos]) - 1,
147 fClusters.metrics().getForceStrut());
148 fWords.extend(fClusters);
149 fBrokeLineWithHyphen = true;
150 return false;
151 }
152 // else let the existing implementation do its best efforts
153 return true;
154 }
155
156 // Since we allow cluster clipping when they don't fit
157 // we have to work with stretches - parts of clusters
lookAhead(SkScalar maxWidth,Cluster * endOfClusters,bool applyRoundingHack,WordBreakType wordBreakType,bool needEllipsis)158 void TextWrapper::lookAhead(SkScalar maxWidth, Cluster* endOfClusters, bool applyRoundingHack,
159 WordBreakType wordBreakType, bool needEllipsis) {
160
161 reset();
162 fEndLine.metrics().clean();
163 fWords.startFrom(fEndLine.startCluster(), fEndLine.startPos());
164 fClusters.startFrom(fEndLine.startCluster(), fEndLine.startPos());
165 fClip.startFrom(fEndLine.startCluster(), fEndLine.startPos());
166
167 bool isFirstWord = true;
168 TextTabAlign textTabAlign(endOfClusters->getOwner()->paragraphStyle().getTextTab());
169 textTabAlign.init(maxWidth, endOfClusters);
170
171 LineBreakerWithLittleRounding breaker(maxWidth, applyRoundingHack);
172 Cluster* nextNonBreakingSpace = nullptr;
173 SkScalar totalFakeSpacing = 0.0;
174 bool attemptedHyphenate = false;
175
176 for (auto cluster = fEndLine.endCluster(); cluster < endOfClusters; ++cluster) {
177 totalFakeSpacing += (cluster->needAutoSpacing() && cluster != fEndLine.endCluster()) ?
178 (cluster - 1)->getFontSize() / AUTO_SPACING_WIDTH_RATIO : 0;
179 SkScalar widthBeforeCluster = fWords.width() + fClusters.width() + totalFakeSpacing;
180 if (cluster->isHardBreak()) {
181 if (cluster != fEndLine.endCluster()) {
182 isFirstWord = false;
183 }
184 } else if (
185 // TODO: Trying to deal with flutter rounding problem. Must be removed...
186 SkScalar width = cluster->width() + widthBeforeCluster;
187 (!isFirstWord || wordBreakType != WordBreakType::NORMAL) &&
188 breaker.breakLine(width)) {
189 // if the hyphenator has already run as balancing algorithm, use the cluster information
190 if (cluster->isHyphenBreak() && !needEllipsis) {
191 // we dont want to add the current cluster as the hyphenation algorithm marks breaks before a cluster
192 // however, if we cannot fit anything to a line, we need to break out here
193 if (fWords.empty() && fClusters.empty()) {
194 fClusters.extend(cluster);
195 fTooLongCluster = true;
196 break;
197 }
198 if (!fClusters.empty()) {
199 fWords.extend(fClusters);
200 fBrokeLineWithHyphen = true;
201 break;
202 }
203 // let hyphenator try before this if it is enabled
204 } else if (cluster->isWhitespaceBreak() && ((wordBreakType != WordBreakType::BREAK_HYPHEN) ||
205 (wordBreakType == WordBreakType::BREAK_HYPHEN && attemptedHyphenate && !needEllipsis))) {
206 // It's the end of the word
207 isFirstWord = false;
208 fClusters.extend(cluster);
209
210 bool tabAlignRet = false;
211 if (cluster->isTabulation()) {
212 tabAlignRet = textTabAlign.processTab(fWords, fClusters, cluster, totalFakeSpacing);
213 } else {
214 tabAlignRet = textTabAlign.processEndofWord(fWords, fClusters, cluster, totalFakeSpacing);
215 }
216 if (tabAlignRet) {
217 break;
218 }
219 fMinIntrinsicWidth = std::max(fMinIntrinsicWidth, this->getClustersTrimmedWidth());
220 fWords.extend(fClusters);
221 continue;
222 } else if (cluster->run().isPlaceholder()) {
223 isFirstWord = false;
224 if (!fClusters.empty()) {
225 // Placeholder ends the previous word
226 fMinIntrinsicWidth = std::max(fMinIntrinsicWidth, this->getClustersTrimmedWidth());
227 fWords.extend(fClusters);
228 }
229
230 if (cluster->width() > maxWidth && fWords.empty()) {
231 // Placeholder is the only text and it's longer than the line;
232 // it does not count in fMinIntrinsicWidth
233 fClusters.extend(cluster);
234 fTooLongCluster = true;
235 fTooLongWord = true;
236 } else {
237 // Placeholder does not fit the line; it will be considered again on the next line
238 }
239 break;
240 }
241
242 // should do this only if hyphenation is enabled
243 if (wordBreakType == WordBreakType::BREAK_HYPHEN && !attemptedHyphenate && !fClusters.empty() &&
244 !needEllipsis) {
245 attemptedHyphenate = true;
246 if (!lookAheadByHyphen(endOfClusters, widthBeforeCluster, breaker.fUpper)) {
247 break;
248 }
249 }
250
251 textTabAlign.processEndofLine(fWords, fClusters, cluster, totalFakeSpacing);
252
253 // Walk further to see if there is a too long word, cluster or glyph
254 SkScalar nextWordLength = fClusters.width();
255 SkScalar nextShortWordLength = nextWordLength;
256 for (auto further = cluster; further != endOfClusters; ++further) {
257 if (further->isSoftBreak() || further->isHardBreak() || further->isWhitespaceBreak()) {
258 break;
259 }
260 if (further->run().isPlaceholder()) {
261 // Placeholder ends the word
262 break;
263 }
264
265 if (nextWordLength > 0 && nextWordLength <= maxWidth && further->isIntraWordBreak()) {
266 // The cluster is spaces but not the end of the word in a normal sense
267 nextNonBreakingSpace = further;
268 nextShortWordLength = nextWordLength;
269 }
270
271 if (maxWidth == 0) {
272 // This is a tricky flutter case: layout(width:0) places 1 cluster on each line
273 nextWordLength = std::max(nextWordLength, further->width());
274 } else {
275 nextWordLength += further->width();
276 }
277 }
278 if (nextWordLength > maxWidth) {
279 if (nextNonBreakingSpace != nullptr) {
280 // We only get here if the non-breaking space improves our situation
281 // (allows us to break the text to fit the word)
282 if (SkScalar shortLength = fWords.width() + nextShortWordLength;
283 !breaker.breakLine(shortLength)) {
284 // We can add the short word to the existing line
285 fClusters = TextStretch(fClusters.startCluster(), nextNonBreakingSpace, fClusters.metrics().getForceStrut());
286 fMinIntrinsicWidth = std::max(fMinIntrinsicWidth, nextShortWordLength);
287 fWords.extend(fClusters);
288 } else {
289 // We can place the short word on the next line
290 fClusters.clean();
291 }
292 // Either way we are not in "word is too long" situation anymore
293 break;
294 }
295 // If the word is too long we can break it right now and hope it's enough
296 fMinIntrinsicWidth = std::max(fMinIntrinsicWidth, nextWordLength);
297 if (fClusters.endPos() - fClusters.startPos() > 1 ||
298 fWords.empty()) {
299 fTooLongWord = true;
300 } else {
301 // Even if the word is too long there is a very little space on this line.
302 // let's deal with it on the next line.
303 }
304 }
305
306 if (fWords.empty() && breaker.breakLine(cluster->width())) {
307 fClusters.extend(cluster);
308 fTooLongCluster = true;
309 fTooLongWord = true;
310 }
311 break;
312 }
313
314 if (cluster->isSoftBreak() || cluster->isWhitespaceBreak()) {
315 isFirstWord = false;
316 }
317
318 if (cluster->run().isPlaceholder()) {
319 if (!fClusters.empty()) {
320 // Placeholder ends the previous word (placeholders are ignored in trimming)
321 fMinIntrinsicWidth = std::max(fMinIntrinsicWidth, getClustersTrimmedWidth());
322 fWords.extend(fClusters);
323 }
324
325 // Placeholder is separate word and its width now is counted in minIntrinsicWidth
326 fMinIntrinsicWidth = std::max(fMinIntrinsicWidth, cluster->width());
327 fWords.extend(cluster);
328 } else {
329 if (cluster->isTabulation()) {
330 if (textTabAlign.processTab(fWords, fClusters, cluster, totalFakeSpacing)) {
331 break;
332 }
333 fClusters.extend(cluster);
334
335 fMinIntrinsicWidth = std::max(fMinIntrinsicWidth, getClustersTrimmedWidth());
336 fWords.extend(fClusters);
337 } else {
338 fClusters.extend(cluster);
339 if (fClusters.endOfWord()) { // Keep adding clusters/words
340 if (textTabAlign.processEndofWord(fWords, fClusters, cluster, totalFakeSpacing)) {
341 if (wordBreakType == WordBreakType::BREAK_ALL) {
342 fClusters.trim(cluster);
343 }
344 break;
345 }
346
347 fMinIntrinsicWidth = std::max(fMinIntrinsicWidth, getClustersTrimmedWidth());
348 fWords.extend(fClusters);
349 } else {
350 if (textTabAlign.processCluster(fWords, fClusters, cluster, totalFakeSpacing)) {
351 fClusters.trim(cluster);
352 break;
353 }
354 }
355 }
356 }
357
358 if ((fHardLineBreak = cluster->isHardBreak())) {
359 // Stop at the hard line break
360 break;
361 }
362 }
363 }
364
moveForward(bool hasEllipsis,bool breakAll)365 void TextWrapper::moveForward(bool hasEllipsis, bool breakAll) {
366
367 // We normally break lines by words.
368 // The only way we may go to clusters is if the word is too long or
369 // it's the first word and it has an ellipsis attached to it.
370 // If nothing fits we show the clipping.
371 fTooLongWord = breakAll;
372 if (!fWords.empty()) {
373 fEndLine.extend(fWords);
374 #ifdef SK_IGNORE_SKPARAGRAPH_ELLIPSIS_FIX
375 if (!fTooLongWord || hasEllipsis) { // Ellipsis added to a word
376 #else
377 if (!fTooLongWord && !hasEllipsis) { // Ellipsis added to a grapheme
378 #endif
379 return;
380 }
381 }
382 if (!fClusters.empty()) {
383 fEndLine.extend(fClusters);
384 if (!fTooLongCluster) {
385 return;
386 }
387 }
388
389 if (!fClip.empty()) {
390 // Flutter: forget the clipped cluster but keep the metrics
391 fEndLine.metrics().add(fClip.metrics());
392 }
393 }
394
395 // Special case for start/end cluster since they can be clipped
396 void TextWrapper::trimEndSpaces(TextAlign align) {
397 // Remember the breaking position
398 fEndLine.saveBreak();
399 // Skip all space cluster at the end
400 for (auto cluster = fEndLine.endCluster();
401 cluster >= fEndLine.startCluster() && cluster->isWhitespaceBreak();
402 --cluster) {
403 fEndLine.trim(cluster);
404 }
405 fEndLine.trim();
406 }
407
408 SkScalar TextWrapper::getClustersTrimmedWidth() {
409 // Move the end of the line to the left
410 SkScalar width = 0;
411 bool trailingSpaces = true;
412 for (auto cluster = fClusters.endCluster(); cluster >= fClusters.startCluster(); --cluster) {
413 if (cluster->run().isPlaceholder()) {
414 continue;
415 }
416 if (trailingSpaces) {
417 if (!cluster->isWhitespaceBreak()) {
418 width += cluster->trimmedWidth(cluster->endPos());
419 trailingSpaces = false;
420 }
421 continue;
422 }
423 width += cluster->width();
424 }
425 return width;
426 }
427
428 // Trim the beginning spaces in case of soft line break
429 std::tuple<Cluster*, size_t, SkScalar> TextWrapper::trimStartSpaces(Cluster* endOfClusters) {
430
431 if (fHardLineBreak) {
432 // End of line is always end of cluster, but need to skip \n
433 auto width = fEndLine.width();
434 auto cluster = fEndLine.endCluster() + 1;
435 while (cluster < fEndLine.breakCluster() && cluster->isWhitespaceBreak()) {
436 width += cluster->width();
437 ++cluster;
438 }
439 return std::make_tuple(fEndLine.breakCluster() + 1, 0, width);
440 }
441
442 // breakCluster points to the end of the line;
443 // It's a soft line break so we need to move lineStart forward skipping all the spaces
444 auto width = fEndLine.widthWithGhostSpaces();
445 auto cluster = fEndLine.breakCluster() + 1;
446 while (cluster < endOfClusters && cluster->isWhitespaceBreak() && !cluster->isTabulation()) {
447 width += cluster->width();
448 ++cluster;
449 }
450
451 return std::make_tuple(cluster, 0, width);
452 }
453
454 // calculate heuristics for different variants and select the least bad
455
456 // calculate the total space required
457 // define the goal for line numbers (max vs space required).
458 // If text could fit, it has substantially larger score compared to nicer wrap with overflow
459
460 // iterate: select nontrivial candidates with some maximum offset and set the penalty / benefit of variants
461 // goals: 0) fit maximum amount of text
462 // 1) fill lines
463 // 2) make line lengths even
464 // 2.5) define a cost for hyphenation - not done
465 // 3) try to make it fast
466
467 constexpr int64_t MINIMUM_FILL_RATIO = 75;
468 constexpr int64_t MINIMUM_FILL_RATIO_SQUARED = MINIMUM_FILL_RATIO * MINIMUM_FILL_RATIO;
469 constexpr int64_t GOOD_ENOUGH_LINE_SCORE = 95 * 95;
470 constexpr int64_t UNDERFLOW_SCORE = 100;
471 constexpr float BALANCED_LAST_LINE_MULTIPLIER = 1.4f;
472 constexpr int64_t BEST_LOCAL_SCORE = -1000000;
473 constexpr float WIDTH_TOLERANCE = 5.f;
474 constexpr int64_t PARAM_2 = 2;
475 constexpr int64_t PARAM_10000 = 10000;
476
477 // mkay, this makes an assumption that we do the scoring runs in a single thread and holds the variables during
478 // recursion
479 struct TextWrapScorer {
480 TextWrapScorer(SkScalar maxWidth, ParagraphImpl& parent, size_t maxLines)
481 : maxWidth_(maxWidth), currentTarget_(maxWidth), maxLines_(maxLines), parent_(parent)
482 {
483 CalculateCumulativeLen(parent);
484
485 if (parent_.getLineBreakStrategy() == LineBreakStrategy::BALANCED) {
486 // calculate target width before breaks
487 int64_t targetLines = 1 + cumulativeLen_ / maxWidth_;
488 currentTarget_ = cumulativeLen_ / targetLines;
489 }
490
491 GenerateBreaks(parent);
492 }
493
494 void GenerateBreaks(ParagraphImpl& parent)
495 {
496 // we trust that clusters are sorted on parent
497 bool prevWasWhitespace = false;
498 SkScalar currentWidth = 0;
499 size_t currentCount = 0; // in principle currentWidth != 0 should provide same result
500 SkScalar cumulativeLen = 0;
501 for (size_t ix = 0; ix < parent.clusters().size(); ix++) {
502 auto& cluster = parent.clusters()[ix];
503 auto len = cluster.width();
504 cumulativeLen += len;
505 currentWidth += len;
506 currentCount++;
507 if (cluster.isWhitespaceBreak()) {
508 breaks_.emplace_back(cumulativeLen, Break::BreakType::BREAKTYPE_WHITE_SPACE, prevWasWhitespace);
509 prevWasWhitespace = true;
510 currentWidth = 0;
511 currentCount = 0;
512 } else if (cluster.isHardBreak()) {
513 breaks_.emplace_back(cumulativeLen, Break::BreakType::BREAKTYPE_HARD, false);
514 prevWasWhitespace = true;
515 currentWidth = 0;
516 currentCount = 0;
517 } else if (cluster.isHyphenBreak()) {
518 breaks_.emplace_back(cumulativeLen - cluster.width() + cluster.height(),
519 Break::BreakType::BREAKTYPE_HYPHEN, false);
520 breaks_.back().reservedSpace = cluster.height();
521 prevWasWhitespace = true;
522 currentWidth = 0;
523 currentCount = 0;
524 } else if (cluster.isIntraWordBreak()) {
525 breaks_.emplace_back(cumulativeLen, Break::BreakType::BREAKTYPE_INTRA, false);
526 prevWasWhitespace = true;
527 currentWidth = 0;
528 currentCount = 0;
529 } else if (currentWidth > currentTarget_) {
530 if (currentCount > 1) {
531 cumulativeLen -= cluster.width();
532 ix--;
533 }
534 breaks_.emplace_back(cumulativeLen, Break::BreakType::BREAKTYPE_FORCED, false);
535 prevWasWhitespace = false;
536 currentWidth = 0;
537 currentCount = 0;
538 } else {
539 prevWasWhitespace = false;
540 }
541 }
542 }
543
544 void CalculateCumulativeLen(ParagraphImpl& parent)
545 {
546 auto startCluster = &parent.cluster(0);
547 auto endCluster = &parent.cluster(0);
548 auto locale = parent.paragraphStyle().getTextStyle().getLocale();
549 for (size_t clusterIx = 0; clusterIx < parent.clusters().size(); clusterIx++) {
550 if (parent.getLineBreakStrategy() == LineBreakStrategy::BALANCED) {
551 auto& cluster = parent.cluster(clusterIx);
552 auto len = cluster.width();
553 cumulativeLen_ += len;
554 }
555 CalculateHyphenPos(clusterIx, startCluster, endCluster, parent, locale);
556 }
557 }
558
559 void CalculateHyphenPos(size_t clusterIx, Cluster*& startCluster, Cluster*& endCluster, ParagraphImpl& parent,
560 const SkString& locale)
561 {
562 auto& cluster = parent.cluster(clusterIx);
563 const bool hyphenEnabled = parent.getWordBreakType() == WordBreakType::BREAK_HYPHEN;
564 bool isWhitespace = (cluster.isHardBreak() || cluster.isWhitespaceBreak() || cluster.isTabulation());
565 if (hyphenEnabled && !fPrevWasWhitespace && isWhitespace && endCluster > startCluster) {
566 fPrevWasWhitespace = true;
567 auto results = Hyphenator::getInstance().findBreakPositions(
568 locale, parent.fText, startCluster->textRange().start, endCluster->textRange().end);
569 CheckHyphenBreak(results, parent, startCluster);
570 if (clusterIx + 1 < parent.clusters().size()) {
571 startCluster = &cluster + 1;
572 }
573 } else if (!isWhitespace) {
574 fPrevWasWhitespace = false;
575 endCluster = &cluster;
576 } else { // fix "one character + space and then the actual target"
577 uint32_t i = 1;
578 while (clusterIx + i < parent.clusters().size()) {
579 if (!parent.cluster(clusterIx + i).isWordBreak()) {
580 startCluster = &cluster + i;
581 break;
582 } else {
583 i++;
584 }
585 }
586 }
587 }
588
589 void CheckHyphenBreak(std::vector<uint8_t> results, ParagraphImpl& parent, Cluster*& startCluster)
590 {
591 size_t prevClusterIx = 0;
592 for (size_t resultIx = 0; resultIx < results.size(); resultIx++) {
593 if (results[resultIx] & 0x1) {
594 auto clusterPos = parent.clusterIndex(startCluster->textRange().start + resultIx);
595 if (clusterPos != prevClusterIx) {
596 parent.cluster(clusterPos).enableHyphenBreak();
597 prevClusterIx = clusterPos;
598 }
599 }
600 }
601 }
602
603 struct RecursiveParam {
604 int64_t targetLines;
605 size_t maxLines;
606 size_t lineNumber;
607 SkScalar begin;
608 SkScalar remainingTextWidth;
609 SkScalar currentMax;
610 size_t breakPos;
611 };
612
613 void Run() {
614 int64_t targetLines = 1 + cumulativeLen_ / maxWidth_;
615
616 if (parent_.getLineBreakStrategy() == LineBreakStrategy::BALANCED) {
617 currentTarget_ = cumulativeLen_ / targetLines;
618 }
619
620 if (targetLines < PARAM_2) {
621 // need to have at least two lines for algo to do anything useful
622 return;
623 }
624 CalculateRecursive(RecursiveParam{
625 targetLines, maxLines_, 0, 0.f, cumulativeLen_,
626 });
627 LOGD("cache_: %{public}zu", cache_.size());
628 }
629
630 int64_t CalculateRecursive(RecursiveParam param)
631 {
632 if (param.maxLines == 0 || param.remainingTextWidth <= 1.f) {
633 return BEST_LOCAL_SCORE;
634 }
635
636 // This should come precalculated
637 param.currentMax = maxWidth_ - parent_.detectIndents(param.lineNumber);
638 if (nearlyZero(param.currentMax)) {
639 return BEST_LOCAL_SCORE;
640 }
641
642 // trim possible spaces at the beginning of line
643 while ((param.lineNumber > 0) && (lastBreakPos_ + 1 < breaks_.size()) &&
644 (breaks_[lastBreakPos_ + 1].subsequentWhitespace)) {
645 param.remainingTextWidth += (param.begin - breaks_[++lastBreakPos_].width);
646 param.begin = breaks_[lastBreakPos_].width;
647 }
648
649 if (lastBreakPos_ < breaks_.size() && breaks_[lastBreakPos_].type == Break::BreakType::BREAKTYPE_FORCED) {
650 lastBreakPos_++;
651 }
652 param.breakPos = lastBreakPos_;
653
654 while (param.breakPos < breaks_.size() && breaks_[param.breakPos].width < (param.begin + param.currentMax)) {
655 param.breakPos++;
656 }
657
658 if (param.breakPos == lastBreakPos_ && param.remainingTextWidth > param.currentMax) {
659 // If we were unable to find a break that matches the criteria, insert new one
660 // This may happen if there is a long word and per line indent for this particular line
661 if (param.breakPos + 1 > breaks_.size()) {
662 breaks_.emplace_back(param.begin + param.currentMax, Break::BreakType::BREAKTYPE_FORCED, false);
663 } else {
664 breaks_.insert(breaks_.cbegin() + param.breakPos + 1, Break(param.begin + param.currentMax,
665 Break::BreakType::BREAKTYPE_FORCED, false));
666 }
667 param.breakPos += BREAK_NUM_TWO;
668 }
669
670 LOGD("Line %{public}lu about to loop %{public}f, %{public}lu, %{public}lu, max: %{public}f",
671 static_cast<unsigned long>(param.lineNumber), param.begin, static_cast<unsigned long>(param.breakPos),
672 static_cast<unsigned long>(lastBreakPos_), maxWidth_);
673
674 return FindOptimalSolutionForCurrentLine(param);
675 }
676
677 std::vector<SkScalar>& GetResult()
678 {
679 return current_;
680 }
681
682 SkScalar calculateCurrentWidth(RecursiveParam& param, bool looped)
683 {
684 SkScalar newWidth = param.currentMax;
685
686 if (param.breakPos > 0 && param.begin < breaks_[param.breakPos - 1].width) {
687 newWidth = std::min(breaks_[--param.breakPos].width - param.begin, param.currentMax);
688 }
689
690 if (looped
691 && ((lastBreakPos_ == param.breakPos)
692 || (newWidth / param.currentMax * UNDERFLOW_SCORE < MINIMUM_FILL_RATIO))) {
693 LOGD("line %{public}lu breaking %{public}f, %{public}lu, %{public}f/%{public}f",
694 static_cast<unsigned long>(param.lineNumber), param.begin, static_cast<unsigned long>(param.breakPos),
695 newWidth, maxWidth_);
696 return 0;
697 }
698
699 lastBreakPos_ = param.breakPos;
700
701 return std::min(newWidth, param.remainingTextWidth);
702 }
703
704 int64_t FindOptimalSolutionForCurrentLine(RecursiveParam& param)
705 {
706 // have this in reversed order to avoid extra insertions
707 std::vector<SkScalar> currentBest;
708 bool looped = false;
709 int64_t score = 0;
710 int64_t overallScore = score;
711 int64_t bestLocalScore = BEST_LOCAL_SCORE;
712 do {
713 // until the given threshold is crossed (minimum line fill rate)
714 // re-break this line, if a result is different, calculate score
715 SkScalar currentWidth = calculateCurrentWidth(param, looped);
716 if (currentWidth == 0) {
717 break;
718 }
719 Index index { param.lineNumber, param.begin, currentWidth };
720
721 // check cache
722 const auto& ite = cache_.find(index);
723 if (ite != cache_.cend()) {
724 cacheHits_++;
725 current_ = ite->second.widths;
726 overallScore = ite->second.score;
727 UpdateSolution(bestLocalScore, overallScore, currentBest);
728 looped = true;
729 continue;
730 }
731 SkScalar scoref = std::min(1.f, abs(currentTarget_ - currentWidth) / currentTarget_);
732 score = int64_t((1.f - scoref) * UNDERFLOW_SCORE);
733 score *= score;
734
735 current_.clear();
736 overallScore = score;
737
738 // Handle last line
739 if (breaks_[param.breakPos].type == Break::BreakType::BREAKTYPE_HYPHEN) {
740 auto copy = currentWidth - breaks_[param.breakPos].reservedSpace;
741 // name is bit confusing as the method enters also recursion
742 // with hyphen break this never is the last line
743 if (!HandleLastLine(param, overallScore, copy, score)) {
744 break;
745 }
746 } else { // real last line may update currentWidth
747 if (!HandleLastLine(param, overallScore, currentWidth, score)) {
748 break;
749 }
750 }
751 // we have exceeded target number of lines, add some penalty
752 if (param.targetLines < 0) {
753 overallScore += param.targetLines * PARAM_10000; // MINIMUM_FILL_RATIO;
754 }
755
756 // We always hold the best possible score of children at this point
757 current_.push_back(currentWidth);
758 cache_[index] = { overallScore, current_ };
759
760 UpdateSolution(bestLocalScore, overallScore, currentBest);
761 looped = true;
762 } while (score > MINIMUM_FILL_RATIO_SQUARED &&
763 !(param.lineNumber == 0 && bestLocalScore > param.targetLines * GOOD_ENOUGH_LINE_SCORE));
764 current_ = currentBest;
765 return bestLocalScore;
766 }
767
768 bool HandleLastLine(RecursiveParam& param, int64_t& overallScore, SkScalar& currentWidth, int64_t&score)
769 {
770 // Handle last line
771 if (abs(currentWidth - param.remainingTextWidth) < 1.f) {
772 // this is last line, with high-quality wrapping, relax the score a bit
773 if (parent_.getLineBreakStrategy() == LineBreakStrategy::HIGH_QUALITY) {
774 overallScore = std::max(MINIMUM_FILL_RATIO, overallScore);
775 } else {
776 overallScore *= BALANCED_LAST_LINE_MULTIPLIER;
777 }
778
779 // let's break the loop, under no same condition / fill-rate added rows can result to a better
780 // score.
781 currentWidth = param.currentMax;
782 score = MINIMUM_FILL_RATIO_SQUARED - 1;
783 LOGD("last line %{public}lu reached", static_cast<unsigned long>(param.lineNumber));
784 return true;
785 }
786 if (((param.remainingTextWidth - currentWidth) / maxWidth_) < param.maxLines) {
787 // recursively calculate best score for children
788 overallScore += CalculateRecursive(RecursiveParam{
789 param.targetLines - 1,
790 param.maxLines > param.lineNumber ? param.maxLines - param.lineNumber : 0,
791 param.lineNumber + 1,
792 param.begin + currentWidth,
793 param.remainingTextWidth - currentWidth
794 });
795 lastBreakPos_ = param.breakPos; // restore our ix
796 return true;
797 }
798
799 // the text is not going to fit anyway (anymore), no need to push it
800 return false;
801 }
802
803 void UpdateSolution(int64_t& bestLocalScore, const int64_t overallScore, std::vector<SkScalar>& currentBest)
804 {
805 if (overallScore > bestLocalScore) {
806 bestLocalScore = overallScore;
807 currentBest = current_;
808 }
809 }
810
811 private:
812 struct Index {
813 size_t lineNumber { 0 };
814 SkScalar begin { 0 };
815 SkScalar width { 0 };
816 bool operator==(const Index& other) const
817 {
818 return (lineNumber == other.lineNumber && fabs(begin - other.begin) < WIDTH_TOLERANCE &&
819 fabs(width - other.width) < WIDTH_TOLERANCE);
820 }
821 bool operator<(const Index& other) const
822 {
823 return lineNumber < other.lineNumber ||
824 (lineNumber == other.lineNumber && other.begin - begin > WIDTH_TOLERANCE) ||
825 (lineNumber == other.lineNumber && fabs(begin - other.begin) < WIDTH_TOLERANCE &&
826 other.width - width > WIDTH_TOLERANCE);
827 }
828 };
829
830 struct Score {
831 int64_t score { 0 };
832 // in reversed order
833 std::vector<SkScalar> widths;
834 };
835
836 // to be seen if unordered map would be better fit
837 std::map<Index, Score> cache_;
838
839 SkScalar maxWidth_ { 0 };
840 SkScalar currentTarget_ { 0 };
841 SkScalar cumulativeLen_ { 0 };
842 size_t maxLines_ { 0 };
843 ParagraphImpl& parent_;
844 std::vector<SkScalar> current_;
845
846 struct Break {
847 enum class BreakType {
848 BREAKTYPE_NONE,
849 BREAKTYPE_HARD,
850 BREAKTYPE_WHITE_SPACE,
851 BREAKTYPE_INTRA,
852 BREAKTYPE_FORCED,
853 BREAKTYPE_HYPHEN
854 };
855 Break(SkScalar w, BreakType t, bool ssws) : width(w), type(t), subsequentWhitespace(ssws) {}
856
857 SkScalar width { 0.f };
858 BreakType type { BreakType::BREAKTYPE_NONE };
859 bool subsequentWhitespace { false };
860 SkScalar reservedSpace { 0.f };
861 };
862
863 std::vector<Break> breaks_;
864 size_t lastBreakPos_ { 0 };
865
866 uint64_t cacheHits_ { 0 };
867 bool fPrevWasWhitespace{false};
868 };
869
870 uint64_t TextWrapper::CalculateBestScore(std::vector<SkScalar>& widthOut, SkScalar maxWidth,
871 ParagraphImpl* parent, size_t maxLines) {
872 if (maxLines == 0 || !parent || nearlyZero(maxWidth)) {
873 return -1;
874 }
875
876 TextWrapScorer* scorer = new TextWrapScorer(maxWidth, *parent, maxLines);
877 scorer->Run();
878 while (scorer && scorer->GetResult().size()) {
879 auto width = scorer->GetResult().back();
880 widthOut.push_back(width);
881 LOGD("width %{public}f", width);
882 scorer->GetResult().pop_back();
883 }
884
885 delete scorer;
886 return 0;
887 }
888
889 void TextWrapper::updateMetricsWithPlaceholder(std::vector<Run*>& runs, bool iterateByCluster) {
890 if (!iterateByCluster) {
891 Run* lastRun = nullptr;
892 for (auto& run : runs) {
893 if (run == lastRun) {
894 continue;
895 }
896 lastRun = run;
897 if (lastRun != nullptr && lastRun->placeholderStyle() != nullptr) {
898 SkASSERT(lastRun->size() == 1);
899 // Update the placeholder metrics so we can get the placeholder positions later
900 // and the line metrics (to make sure the placeholder fits)
901 lastRun->updateMetrics(&fEndLine.metrics());
902 }
903 }
904 return;
905 }
906 runs.clear();
907 // Deal with placeholder clusters == runs[@size==1]
908 Run* lastRun = nullptr;
909 for (auto cluster = fEndLine.startCluster(); cluster <= fEndLine.endCluster(); ++cluster) {
910 auto r = cluster->runOrNull();
911 if (r == lastRun) {
912 continue;
913 }
914 lastRun = r;
915 if (lastRun != nullptr && lastRun->placeholderStyle() != nullptr) {
916 SkASSERT(lastRun->size() == 1);
917 // Update the placeholder metrics so we can get the placeholder positions later
918 // and the line metrics (to make sure the placeholder fits)
919 lastRun->updateMetrics(&fEndLine.metrics());
920 runs.emplace_back(lastRun);
921 }
922 }
923 }
924
925 // TODO: refactor the code for line ending (with/without ellipsis)
926 void TextWrapper::breakTextIntoLines(ParagraphImpl* parent,
927 SkScalar maxWidth,
928 const AddLineToParagraph& addLine) {
929 initParent(parent);
930
931 if (fParent->getLineBreakStrategy() == LineBreakStrategy::BALANCED &&
932 fParent->getWordBreakType() != WordBreakType::BREAK_ALL &&
933 fParent->getWordBreakType() != WordBreakType::BREAK_HYPHEN) {
934 layoutLinesBalanced(parent, maxWidth, addLine);
935 return;
936 }
937
938 layoutLinesSimple(parent, maxWidth, addLine);
939 }
940
941 void TextWrapper::layoutLinesSimple(ParagraphImpl* parent,
942 SkScalar maxWidth,
943 const AddLineToParagraph& addLine) {
944 auto span = parent->clusters();
945 if (span.empty()) {
946 return;
947 }
948 auto maxLines = parent->paragraphStyle().getMaxLines();
949 auto align = parent->paragraphStyle().effective_align();
950 auto unlimitedLines = maxLines == std::numeric_limits<size_t>::max();
951 auto endlessLine = !SkScalarIsFinite(maxWidth);
952 auto hasEllipsis = parent->paragraphStyle().ellipsized();
953
954 auto disableFirstAscent = parent->paragraphStyle().getTextHeightBehavior() & TextHeightBehavior::kDisableFirstAscent;
955 auto disableLastDescent = parent->paragraphStyle().getTextHeightBehavior() & TextHeightBehavior::kDisableLastDescent;
956 bool firstLine = true; // We only interested in fist line if we have to disable the first ascent
957
958 // Resolve balanced line widths
959 std::vector<SkScalar> balancedWidths;
960
961 // if word breaking strategy is nontrivial (balanced / optimal), AND word break mode is not BREAK_ALL
962 if (parent->getWordBreakType() != WordBreakType::BREAK_ALL &&
963 parent->getLineBreakStrategy() != LineBreakStrategy::GREEDY) {
964 if (CalculateBestScore(balancedWidths, maxWidth, parent, maxLines) < 0) {
965 // if the line breaking strategy returns a negative score, the algorithm could not fit or break the text
966 // fall back to default, greedy algorithm
967 balancedWidths.clear();
968 }
969 LOGD("Got %{public}lu", static_cast<unsigned long>(balancedWidths.size()));
970 }
971
972 SkScalar softLineMaxIntrinsicWidth = 0;
973 fEndLine = TextStretch(span.begin(), span.begin(), parent->strutForceHeight() && parent->strutEnabled());
974 auto end = span.end() - 1;
975 auto start = span.begin();
976 InternalLineMetrics maxRunMetrics;
977 bool needEllipsis = false;
978 SkScalar newWidth = maxWidth;
979 SkScalar noIndentWidth = maxWidth;
980 while (fEndLine.endCluster() != end) {
981 noIndentWidth = maxWidth - parent->detectIndents(fLineNumber - 1);
982 if (maxLines == 1 &&
983 (parent->paragraphStyle().getEllipsisMod() == EllipsisModal::HEAD ||
984 parent->needCreateMiddleEllipsis())) {
985 newWidth = FLT_MAX;
986 } else if (!balancedWidths.empty() && fLineNumber - 1 < balancedWidths.size()) {
987 newWidth = balancedWidths[fLineNumber - 1];
988 } else {
989 newWidth = maxWidth - parent->detectIndents(fLineNumber - 1);
990 }
991 auto lastLine = (hasEllipsis && unlimitedLines) || fLineNumber >= maxLines;
992 needEllipsis = hasEllipsis && !endlessLine && lastLine;
993
994 this->lookAhead(newWidth, end, parent->getApplyRoundingHack(), parent->getWordBreakType(), needEllipsis);
995
996 this->moveForward(needEllipsis, parent->getWordBreakType() == WordBreakType::BREAK_ALL);
997 if (fEndLine.endCluster() >= fEndLine.startCluster() || maxLines > 1) {
998 needEllipsis &= fEndLine.endCluster() < end - 1; // Only if we have some text to ellipsize
999 }
1000
1001 // Do not trim end spaces on the naturally last line of the left aligned text
1002 this->trimEndSpaces(align);
1003
1004 // For soft line breaks add to the line all the spaces next to it
1005 Cluster* startLine;
1006 size_t pos;
1007 SkScalar widthWithSpaces;
1008 std::tie(startLine, pos, widthWithSpaces) = this->trimStartSpaces(end);
1009
1010 if (needEllipsis && !fHardLineBreak) {
1011 // This is what we need to do to preserve a space before the ellipsis
1012 fEndLine.restoreBreak();
1013 widthWithSpaces = fEndLine.widthWithGhostSpaces();
1014 } else if (fBrokeLineWithHyphen) {
1015 fEndLine.shiftWidth(fEndLine.endCluster()->width());
1016 }
1017
1018 // If the line is empty with the hard line break, let's take the paragraph font (flutter???)
1019 if (fEndLine.metrics().isClean()) {
1020 fEndLine.setMetrics(parent->getEmptyMetrics());
1021 }
1022
1023 std::vector<Run*> runs;
1024 updateMetricsWithPlaceholder(runs, true);
1025 // update again for some case
1026 // such as :
1027 // placeholderA(width: 100, height: 100, align: bottom) placeholderB(width: 200, height: 200, align: top)
1028 // without second update: the placeholderA bottom will be set 0, and placeholderB bottom will be set 100
1029 // so the fEndline bottom will be set 100, is not equal placeholderA bottom
1030 updateMetricsWithPlaceholder(runs, false);
1031 // Before we update the line metrics with struts,
1032 // let's save it for GetRectsForRange(RectHeightStyle::kMax)
1033 maxRunMetrics = fEndLine.metrics();
1034 maxRunMetrics.fForceStrut = false;
1035
1036 // TODO: keep start/end/break info for text and runs but in a better way that below
1037 TextRange textExcludingSpaces(fEndLine.startCluster()->textRange().start, fEndLine.endCluster()->textRange().end);
1038 TextRange text(fEndLine.startCluster()->textRange().start, fEndLine.breakCluster()->textRange().start);
1039 TextRange textIncludingNewlines(fEndLine.startCluster()->textRange().start, startLine->textRange().start);
1040 if (startLine == end) {
1041 textIncludingNewlines.end = parent->text().size();
1042 text.end = parent->text().size();
1043 }
1044 ClusterRange clusters(fEndLine.startCluster() - start, fEndLine.endCluster() - start + 1);
1045 ClusterRange clustersWithGhosts(fEndLine.startCluster() - start, startLine - start);
1046
1047 if (disableFirstAscent && firstLine) {
1048 fEndLine.metrics().fAscent = fEndLine.metrics().fRawAscent;
1049 }
1050 if (disableLastDescent && (lastLine || (startLine == end && !fHardLineBreak))) {
1051 fEndLine.metrics().fDescent = fEndLine.metrics().fRawDescent;
1052 }
1053
1054 if (parent->strutEnabled()) {
1055 // Make sure font metrics are not less than the strut
1056 parent->strutMetrics().updateLineMetrics(fEndLine.metrics());
1057 }
1058
1059 SkScalar lineHeight = fEndLine.metrics().height();
1060 if (fHardLineBreak && !lastLine && parent->paragraphStyle().getParagraphSpacing() > 0) {
1061 lineHeight += parent->paragraphStyle().getParagraphSpacing();
1062 }
1063 firstLine = false;
1064
1065 if (fEndLine.empty()) {
1066 // Correct text and clusters (make it empty for an empty line)
1067 textExcludingSpaces.end = textExcludingSpaces.start;
1068 clusters.end = clusters.start;
1069 }
1070
1071 // In case of a force wrapping we don't have a break cluster and have to use the end cluster
1072 text.end = std::max(text.end, textExcludingSpaces.end);
1073
1074 if (parent->paragraphStyle().getEllipsisMod() == EllipsisModal::HEAD && hasEllipsis) {
1075 needEllipsis = maxLines <= 1;
1076 if (needEllipsis) {
1077 fHardLineBreak = false;
1078 }
1079 }
1080
1081 SkScalar offsetX = parent->detectIndents(fLineNumber - 1);
1082 addLine(textExcludingSpaces,
1083 text,
1084 textIncludingNewlines, clusters, clustersWithGhosts, widthWithSpaces,
1085 fEndLine.startPos(),
1086 fEndLine.endPos(),
1087 SkVector::Make(offsetX, fHeight),
1088 SkVector::Make(fEndLine.width(), lineHeight),
1089 fEndLine.metrics(),
1090 needEllipsis,
1091 offsetX,
1092 noIndentWidth);
1093
1094 softLineMaxIntrinsicWidth += widthWithSpaces;
1095
1096 fMaxIntrinsicWidth = std::max(fMaxIntrinsicWidth, softLineMaxIntrinsicWidth);
1097 if (fHardLineBreak) {
1098 softLineMaxIntrinsicWidth = 0;
1099 }
1100 // Start a new line
1101 fHeight += lineHeight;
1102 if (!fHardLineBreak || startLine != end) {
1103 fEndLine.clean();
1104 }
1105 fEndLine.startFrom(startLine, pos);
1106 parent->fMaxWidthWithTrailingSpaces = std::max(parent->fMaxWidthWithTrailingSpaces, widthWithSpaces);
1107
1108 if (hasEllipsis && unlimitedLines) {
1109 // There is one case when we need an ellipsis on a separate line
1110 // after a line break when width is infinite
1111 if (!fHardLineBreak) {
1112 break;
1113 }
1114 } else if (lastLine) {
1115 // There is nothing more to draw
1116 fHardLineBreak = false;
1117 break;
1118 }
1119
1120 ++fLineNumber;
1121 }
1122 if (parent->paragraphStyle().getIsEndAddParagraphSpacing() &&
1123 parent->paragraphStyle().getParagraphSpacing() > 0) {
1124 fHeight += parent->paragraphStyle().getParagraphSpacing();
1125 }
1126
1127 // We finished formatting the text but we need to scan the rest for some numbers
1128 // TODO: make it a case of a normal flow
1129 if (fEndLine.endCluster() != nullptr) {
1130 auto lastWordLength = 0.0f;
1131 auto cluster = fEndLine.endCluster();
1132 while (cluster != end || cluster->endPos() < end->endPos()) {
1133 fExceededMaxLines = true;
1134 if (cluster->isHardBreak()) {
1135 // Hard line break ends the word and the line
1136 fMaxIntrinsicWidth = std::max(fMaxIntrinsicWidth, softLineMaxIntrinsicWidth);
1137 softLineMaxIntrinsicWidth = 0;
1138 fMinIntrinsicWidth = std::max(fMinIntrinsicWidth, lastWordLength);
1139 lastWordLength = 0;
1140 } else if (cluster->isWhitespaceBreak()) {
1141 // Whitespaces end the word
1142 softLineMaxIntrinsicWidth += cluster->width();
1143 fMinIntrinsicWidth = std::max(fMinIntrinsicWidth, lastWordLength);
1144 lastWordLength = 0;
1145 } else if (cluster->run().isPlaceholder()) {
1146 // Placeholder ends the previous word and creates a separate one
1147 fMinIntrinsicWidth = std::max(fMinIntrinsicWidth, lastWordLength);
1148 // Placeholder width now counts in fMinIntrinsicWidth
1149 softLineMaxIntrinsicWidth += cluster->width();
1150 fMinIntrinsicWidth = std::max(fMinIntrinsicWidth, cluster->width());
1151 lastWordLength = 0;
1152 } else {
1153 // Nothing out of ordinary - just add this cluster to the word and to the line
1154 softLineMaxIntrinsicWidth += cluster->width();
1155 lastWordLength += cluster->width();
1156 }
1157 ++cluster;
1158 }
1159 fMinIntrinsicWidth = std::max(fMinIntrinsicWidth, lastWordLength);
1160 fMaxIntrinsicWidth = std::max(fMaxIntrinsicWidth, softLineMaxIntrinsicWidth);
1161
1162 if (parent->lines().empty()) {
1163 // In case we could not place even a single cluster on the line
1164 if (disableFirstAscent) {
1165 fEndLine.metrics().fAscent = fEndLine.metrics().fRawAscent;
1166 }
1167 if (disableLastDescent && !fHardLineBreak) {
1168 fEndLine.metrics().fDescent = fEndLine.metrics().fRawDescent;
1169 }
1170 fHeight = std::max(fHeight, fEndLine.metrics().height());
1171 }
1172 }
1173
1174 if (fHardLineBreak) {
1175 if (disableLastDescent) {
1176 fEndLine.metrics().fDescent = fEndLine.metrics().fRawDescent;
1177 }
1178
1179 // Last character is a line break
1180 if (parent->strutEnabled()) {
1181 // Make sure font metrics are not less than the strut
1182 parent->strutMetrics().updateLineMetrics(fEndLine.metrics());
1183 }
1184
1185 ClusterRange clusters(fEndLine.breakCluster() - start, fEndLine.endCluster() - start);
1186 addLine(fEndLine.breakCluster()->textRange(),
1187 fEndLine.breakCluster()->textRange(),
1188 fEndLine.endCluster()->textRange(),
1189 clusters,
1190 clusters,
1191 0,
1192 0,
1193 0,
1194 SkVector::Make(0, fHeight),
1195 SkVector::Make(0, fEndLine.metrics().height()),
1196 fEndLine.metrics(),
1197 needEllipsis,
1198 parent->detectIndents(fLineNumber - 1),
1199 noIndentWidth);
1200 fHeight += fEndLine.metrics().height();
1201 parent->lines().back().setMaxRunMetrics(maxRunMetrics);
1202 }
1203
1204 if (parent->lines().empty()) {
1205 return;
1206 }
1207 // Correct line metric styles for the first and for the last lines if needed
1208 if (disableFirstAscent) {
1209 parent->lines().front().setAscentStyle(LineMetricStyle::Typographic);
1210 }
1211 if (disableLastDescent) {
1212 parent->lines().back().setDescentStyle(LineMetricStyle::Typographic);
1213 }
1214 }
1215
1216 std::vector<uint8_t> TextWrapper::findBreakPositions(Cluster* startCluster,
1217 Cluster* endOfClusters,
1218 SkScalar widthBeforeCluster,
1219 SkScalar maxWidth) {
1220 auto startPos = startCluster->textRange().start;
1221 auto endPos = startPos;
1222 auto owner = startCluster->getOwner();
1223 for (auto next = startCluster + 1; next != endOfClusters; next++) {
1224 // find the end boundary of current word (hard/soft/whitespace break)
1225 if (next->isWhitespaceBreak() || next->isHardBreak()) {
1226 break;
1227 } else {
1228 endPos = next->textRange().end;
1229 }
1230 }
1231
1232 // Using end position cluster height as an estimate for reserved hyphen width
1233 // hyphen may not be shaped at this point. End pos may be non-drawable, so prefer
1234 // previous glyph before break
1235 auto mappedEnd = owner->fClustersIndexFromCodeUnit[endPos];
1236 auto len = widthBeforeCluster + owner->cluster(mappedEnd > 0 ? mappedEnd - 1 : 0).height();
1237 // ToDo: We can stop here based on hyhpenation frequency setting
1238 // but there is no need to proceed with hyphenation if we don't have space for even hyphen
1239 if (std::isnan(len) || len >= maxWidth) {
1240 return std::vector<uint8_t>{};
1241 }
1242
1243 auto locale = owner->paragraphStyle().getTextStyle().getLocale();
1244 return Hyphenator::getInstance().findBreakPositions(locale, owner->fText, startPos, endPos);
1245 }
1246
1247 void TextWrapper::pushToWordStretches() {
1248 fWordStretches.push_back(fClusters);
1249 fWordWidthGroups.push_back(fClusters.width());
1250 fClusters.clean();
1251 }
1252
1253 void TextWrapper::pushToWordStretchesBatch() {
1254 fWordStretchesBatch.push_back(fWordStretches);
1255 fWordWidthGroupsBatch.push_back(fWordWidthGroups);
1256 fWordStretches.clear();
1257 fWordWidthGroups.clear();
1258 }
1259
1260 void TextWrapper::generateLineStretches(const std::vector<std::pair<size_t, size_t>>& linesGroupInfo,
1261 std::vector<TextStretch>& wordStretches) {
1262 for (const std::pair<size_t, size_t>& pair : linesGroupInfo) {
1263 TextStretch endLine{};
1264 for (size_t i = pair.first; i <= pair.second; ++i) {
1265 if (i == pair.first) {
1266 endLine.setStartCluster(wordStretches[i].startCluster());
1267 }
1268 endLine.extend(wordStretches[i]);
1269 }
1270 fLineStretches.push_back(endLine);
1271 }
1272 }
1273
1274 void TextWrapper::extendCommonCluster(Cluster* cluster, TextTabAlign& textTabAlign,
1275 SkScalar& totalFakeSpacing, WordBreakType wordBreakType) {
1276 if (cluster->isTabulation()) {
1277 if (textTabAlign.processTab(fWords, fClusters, cluster, totalFakeSpacing)) {
1278 return;
1279 }
1280 fClusters.extend(cluster);
1281
1282 fMinIntrinsicWidth = std::max(fMinIntrinsicWidth, getClustersTrimmedWidth());
1283 pushToWordStretches();
1284 } else {
1285 fClusters.extend(cluster);
1286 if (fClusters.endOfWord()) { // Keep adding clusters/words
1287 textTabAlign.processEndofWord(fWords, fClusters, cluster, totalFakeSpacing);
1288 fMinIntrinsicWidth = std::max(fMinIntrinsicWidth, getClustersTrimmedWidth());
1289 pushToWordStretches();
1290 } else {
1291 if (textTabAlign.processCluster(fWords, fClusters, cluster, totalFakeSpacing)) {
1292 fClusters.trim(cluster);
1293 }
1294 }
1295 }
1296 }
1297
1298 void TextWrapper::generateWordStretches(const SkSpan<Cluster>& span, WordBreakType wordBreakType) {
1299 fEndLine.metrics().clean();
1300
1301 if (fStart == nullptr) {
1302 fStart = span.begin();
1303 fEnd = span.end() - 1;
1304 }
1305
1306 fClusters.startFrom(fStart, fStart->startPos());
1307 fClip.startFrom(fStart, fStart->startPos());
1308
1309 TextTabAlign textTabAlign(fStart->getOwner()->paragraphStyle().getTextTab());
1310 textTabAlign.init(MAX_LINES_LIMIT, fStart);
1311
1312 SkScalar totalFakeSpacing = 0.0;
1313
1314 for (Cluster* cluster = fStart; cluster < fEnd; ++cluster) {
1315 totalFakeSpacing += (cluster->needAutoSpacing() && cluster != fStart)
1316 ? (cluster - 1)->getFontSize() / AUTO_SPACING_WIDTH_RATIO
1317 : 0;
1318 if (cluster->run().isPlaceholder()) {
1319 if (!fClusters.empty()) {
1320 // Placeholder ends the previous word (placeholders are ignored in trimming)
1321 fMinIntrinsicWidth = std::max(fMinIntrinsicWidth, getClustersTrimmedWidth());
1322 pushToWordStretches();
1323 }
1324
1325 // Placeholder is separate word and its width now is counted in minIntrinsicWidth
1326 fMinIntrinsicWidth = std::max(fMinIntrinsicWidth, cluster->width());
1327 fClusters.extend(cluster);
1328 pushToWordStretches();
1329 } else {
1330 extendCommonCluster(cluster, textTabAlign, totalFakeSpacing, wordBreakType);
1331 }
1332
1333 if (cluster->isHardBreak()) {
1334 fStart = cluster;
1335 pushToWordStretchesBatch();
1336 }
1337 }
1338
1339 if (!fEnd->isHardBreak()) {
1340 pushToWordStretchesBatch();
1341 }
1342 }
1343
1344 SkScalar TextWrapper::getTextStretchTrimmedEndSpaceWidth(const TextStretch& stretch) {
1345 SkScalar width = stretch.width();
1346 for (Cluster* cluster = stretch.endCluster(); cluster >= stretch.startCluster(); --cluster) {
1347 if (nearlyEqual(cluster->width(), 0) && cluster->run().isPlaceholder()) {
1348 continue;
1349 }
1350
1351 if (!cluster->isWhitespaceBreak()) {
1352 break;
1353 }
1354 width -= cluster->width();
1355 }
1356 return width;
1357 }
1358
1359 void calculateCostTable(const std::vector<SkScalar>& clustersWidth,
1360 SkScalar maxWidth,
1361 std::vector<SkScalar>& costTable,
1362 std::vector<std::pair<size_t, size_t>>& bestPick) {
1363 int clustersCnt = static_cast<int>(clustersWidth.size());
1364 for (int clustersIndex = clustersCnt - STRATEGY_START_POS; clustersIndex >= 0; --clustersIndex) {
1365 bestPick[clustersIndex].first = static_cast<size_t>(clustersIndex);
1366 SkScalar rowCurrentLen = 0;
1367 std::vector<SkScalar> costList;
1368
1369 int maxWord = clustersIndex;
1370 for (int j = clustersIndex; j < clustersCnt; j++) {
1371 rowCurrentLen += clustersWidth[j];
1372 if (rowCurrentLen > maxWidth) {
1373 rowCurrentLen -= clustersWidth[j];
1374 maxWord = j - 1;
1375 break;
1376 }
1377 maxWord = j;
1378 }
1379 if (maxWord < clustersIndex) {
1380 maxWord = clustersIndex;
1381 }
1382
1383 for (int j = clustersIndex; j <= maxWord; ++j) {
1384 SkScalar cost = std::pow(std::abs(std::accumulate(clustersWidth.begin() + clustersIndex,
1385 clustersWidth.begin() + j + 1,
1386 0) -
1387 maxWidth),
1388 STRATEGY_START_POS);
1389 if (j + 1 <= clustersCnt - 1) {
1390 cost += costTable[j + 1];
1391 }
1392 costList.push_back(cost);
1393 }
1394
1395 SkScalar minCost = *std::min_element(costList.begin(), costList.end());
1396 std::vector<size_t> minCostIndices;
1397 for (size_t q = 0; q < costList.size(); ++q) {
1398 if (nearlyZero(costList[q], minCost)) {
1399 minCostIndices.push_back(q);
1400 }
1401 }
1402 size_t minCostIdx{costList.size() - 1};
1403 if (minCostIndices.size() > 0) {
1404 minCostIdx = minCostIndices[static_cast<int32_t>(minCostIndices.size() / MIN_COST_POS)];
1405 }
1406 costTable[clustersIndex] = minCost;
1407 bestPick[clustersIndex].second = static_cast<size_t>(clustersIndex + minCostIdx);
1408 }
1409 }
1410
1411 std::vector<std::pair<size_t, size_t>> buildWordBalance(
1412 const std::vector<std::pair<size_t, size_t>>& bestPick, int clustersCnt) {
1413 int rowStart{0};
1414 std::vector<std::pair<size_t, size_t>> wordBalance;
1415
1416 while (rowStart < clustersCnt) {
1417 int rowEnd = bestPick[rowStart].second;
1418 wordBalance.emplace_back(rowStart, rowEnd);
1419 rowStart = rowEnd + 1;
1420 }
1421 return wordBalance;
1422 }
1423
1424 std::vector<std::pair<size_t, size_t>> TextWrapper::generateLinesGroupInfo(
1425 const std::vector<SkScalar>& clustersWidth, SkScalar maxWidth) {
1426 std::vector<std::pair<size_t, size_t>> wordBalance;
1427 if (clustersWidth.empty()) {
1428 return wordBalance;
1429 }
1430 int clustersCnt = static_cast<int>(clustersWidth.size());
1431 std::vector<SkScalar> costTable(clustersCnt, 0);
1432 std::vector<std::pair<size_t, size_t>> bestPick(clustersCnt, {0, 0});
1433
1434 costTable[clustersCnt - 1] =
1435 std::pow(std::abs(clustersWidth[clustersCnt - 1] - maxWidth), STRATEGY_START_POS);
1436 bestPick[clustersCnt - 1] = {clustersCnt - 1, clustersCnt - 1};
1437
1438 calculateCostTable(clustersWidth, maxWidth, costTable, bestPick);
1439
1440 return buildWordBalance(bestPick, clustersCnt);
1441 }
1442
1443 std::vector<SkScalar> TextWrapper::generateWordsWidthInfo(const std::vector<TextStretch>& wordStretches) {
1444 std::vector<SkScalar> result;
1445 result.reserve(wordStretches.size());
1446 std::transform(wordStretches.begin(),
1447 wordStretches.end(),
1448 std::back_inserter(result),
1449 [](const TextStretch& word) { return word.width(); });
1450 return result;
1451 }
1452
1453 void TextWrapper::formalizedClusters(std::vector<TextStretch>& clusters, SkScalar limitWidth) {
1454 for (auto it = clusters.begin(); it != clusters.end(); it++) {
1455 if (it->width() < limitWidth) {
1456 continue;
1457 }
1458 std::list<TextStretch> trimmedWordList{*it};
1459
1460 for (auto trimmedItor = trimmedWordList.begin(); trimmedItor != trimmedWordList.end();) {
1461 if (trimmedItor->width() < limitWidth) {
1462 ++trimmedItor;
1463 continue;
1464 }
1465 auto result = trimmedItor->split();
1466 trimmedItor = trimmedWordList.erase(trimmedItor);
1467 trimmedItor = trimmedWordList.insert(trimmedItor, result.begin(), result.end());
1468 std::advance(trimmedItor, result.size());
1469 }
1470
1471 if (trimmedWordList.size() == 0) {
1472 continue;
1473 }
1474
1475 it = clusters.erase(it);
1476 it = clusters.insert(it, trimmedWordList.begin(), trimmedWordList.end());
1477 std::advance(it, trimmedWordList.size() - 1);
1478 }
1479 }
1480
1481 void TextWrapper::generateTextLines(SkScalar maxWidth,
1482 const AddLineToParagraph& addLine,
1483 const SkSpan<Cluster>& span) {
1484 initializeFormattingState(maxWidth, span);
1485 processLineStretches(maxWidth, addLine);
1486 finalizeTextLayout(addLine);
1487 }
1488
1489 void TextWrapper::initializeFormattingState(SkScalar maxWidth, const SkSpan<Cluster>& span) {
1490 const auto& style = fParent->paragraphStyle();
1491 fFormattingContext = {style.getMaxLines() == std::numeric_limits<size_t>::max(),
1492 !SkScalarIsFinite(maxWidth),
1493 style.ellipsized(),
1494 style.getTextHeightBehavior() & TextHeightBehavior::kDisableFirstAscent,
1495 style.getTextHeightBehavior() & TextHeightBehavior::kDisableLastDescent,
1496 style.getMaxLines(),
1497 style.effective_align()};
1498
1499 fFirstLine = true;
1500 fSoftLineMaxIntrinsicWidth = 0;
1501 fNoIndentWidth = maxWidth;
1502 fEnd = span.end() - 1;
1503 fStart = span.begin();
1504 }
1505
1506 void TextWrapper::processLineStretches(SkScalar maxWidth, const AddLineToParagraph& addLine) {
1507 for (TextStretch& line : fLineStretches) {
1508 prepareLineForFormatting(line);
1509 formatCurrentLine(addLine);
1510
1511 advanceToNextLine();
1512 if (shouldBreakFormattingLoop()) {
1513 break;
1514 }
1515 ++fLineNumber;
1516 }
1517 }
1518
1519 void TextWrapper::finalizeTextLayout(const AddLineToParagraph& addLine) {
1520 processRemainingClusters();
1521 addFinalLineBreakIfNeeded(addLine);
1522 adjustFirstLastLineMetrics();
1523
1524 if (fParent->paragraphStyle().getIsEndAddParagraphSpacing() &&
1525 fParent->paragraphStyle().getParagraphSpacing() > 0) {
1526 fHeight += fParent->paragraphStyle().getParagraphSpacing();
1527 }
1528 }
1529
1530 void TextWrapper::prepareLineForFormatting(TextStretch& line) {
1531 fEndLine = std::move(line);
1532 fHardLineBreak = fEndLine.endCluster()->isHardBreak();
1533 }
1534
1535 void TextWrapper::formatCurrentLine(const AddLineToParagraph& addLine) {
1536 bool needEllipsis = determineIfEllipsisNeeded();
1537 trimLineSpaces();
1538 handleSpecialCases(needEllipsis);
1539 updateLineMetrics();
1540 addFormattedLineToParagraph(addLine, needEllipsis);
1541 }
1542
1543 bool TextWrapper::determineIfEllipsisNeeded() {
1544 bool lastLine = (fFormattingContext.hasEllipsis && fFormattingContext.unlimitedLines) ||
1545 fLineNumber >= fFormattingContext.maxLines;
1546 bool needEllipsis =
1547 fFormattingContext.hasEllipsis && !fFormattingContext.endlessLine && lastLine;
1548
1549 if (fParent->paragraphStyle().getEllipsisMod() == EllipsisModal::HEAD &&
1550 fFormattingContext.hasEllipsis) {
1551 needEllipsis = fFormattingContext.maxLines <= 1;
1552 if (needEllipsis) {
1553 fHardLineBreak = false;
1554 }
1555 }
1556
1557 return needEllipsis;
1558 }
1559
1560 void TextWrapper::trimLineSpaces() {
1561 this->trimEndSpaces(fFormattingContext.align);
1562 std::tie(fCurrentStartLine, fCurrentStartPos, fCurrentLineWidthWithSpaces) =
1563 this->trimStartSpaces(fEnd);
1564 }
1565
1566 void TextWrapper::handleSpecialCases(bool needEllipsis) {
1567 if (needEllipsis && !fHardLineBreak) {
1568 fEndLine.restoreBreak();
1569 fCurrentLineWidthWithSpaces = fEndLine.widthWithGhostSpaces();
1570 } else if (fBrokeLineWithHyphen) {
1571 fEndLine.shiftWidth(fEndLine.endCluster()->width());
1572 }
1573 }
1574
1575 void TextWrapper::updateLineMetrics() {
1576 if (fEndLine.metrics().isClean()) {
1577 fEndLine.setMetrics(fParent->getEmptyMetrics());
1578 }
1579 updatePlaceholderMetrics();
1580 adjustLineMetricsForFirstLastLine();
1581 applyStrutMetrics();
1582 }
1583
1584 void TextWrapper::updatePlaceholderMetrics() {
1585 std::vector<Run*> runs;
1586 updateMetricsWithPlaceholder(runs, true);
1587 updateMetricsWithPlaceholder(runs, false);
1588 fMaxRunMetrics = fEndLine.metrics();
1589 fMaxRunMetrics.fForceStrut = false;
1590 }
1591
1592 void TextWrapper::adjustLineMetricsForFirstLastLine() {
1593 if (fFormattingContext.disableFirstAscent && fFirstLine) {
1594 fEndLine.metrics().fAscent = fEndLine.metrics().fRawAscent;
1595 }
1596 if (fFormattingContext.disableLastDescent && isLastLine()) {
1597 fEndLine.metrics().fDescent = fEndLine.metrics().fRawDescent;
1598 }
1599 }
1600
1601 void TextWrapper::applyStrutMetrics() {
1602 if (fParent->strutEnabled()) {
1603 fParent->strutMetrics().updateLineMetrics(fEndLine.metrics());
1604 }
1605 }
1606
1607 TextWrapper::LineTextRanges TextWrapper::calculateLineTextRanges() {
1608 LineTextRanges ranges;
1609
1610 ranges.textExcludingSpaces = TextRange(fEndLine.startCluster()->textRange().start,
1611 fEndLine.endCluster()->textRange().end);
1612
1613 ranges.text = TextRange(fEndLine.startCluster()->textRange().start,
1614 fEndLine.breakCluster()->textRange().start);
1615
1616 ranges.textIncludingNewlines = TextRange(fEndLine.startCluster()->textRange().start,
1617 fCurrentStartLine->textRange().start);
1618
1619 if (fCurrentStartLine == fEnd) {
1620 ranges.textIncludingNewlines.end = fParent->text().size();
1621 ranges.text.end = fParent->text().size();
1622 }
1623
1624 ranges.clusters =
1625 ClusterRange(fEndLine.startCluster() - fStart, fEndLine.endCluster() - fStart + 1);
1626
1627 ranges.clustersWithGhosts =
1628 ClusterRange(fEndLine.startCluster() - fStart, fCurrentStartLine - fStart);
1629
1630 if (fEndLine.empty()) {
1631 ranges.textExcludingSpaces.end = ranges.textExcludingSpaces.start;
1632 ranges.clusters.end = ranges.clusters.start;
1633 }
1634
1635 ranges.text.end = std::max(ranges.text.end, ranges.textExcludingSpaces.end);
1636
1637 return ranges;
1638 }
1639
1640 SkScalar TextWrapper::calculateLineHeight() {
1641 SkScalar height = fEndLine.metrics().height();
1642 if (fHardLineBreak && !isLastLine() && fParent->paragraphStyle().getParagraphSpacing() > 0) {
1643 height += fParent->paragraphStyle().getParagraphSpacing();
1644 }
1645 return height;
1646 }
1647
1648 void TextWrapper::addFormattedLineToParagraph(const AddLineToParagraph& addLine,
1649 bool needEllipsis) {
1650 LineTextRanges ranges = calculateLineTextRanges();
1651 SkScalar lineHeight = calculateLineHeight();
1652 SkScalar offsetX = fParent->detectIndents(fLineNumber - 1);
1653
1654 addLine(ranges.textExcludingSpaces,
1655 ranges.text,
1656 ranges.textIncludingNewlines,
1657 ranges.clusters,
1658 ranges.clustersWithGhosts,
1659 fCurrentLineWidthWithSpaces,
1660 fEndLine.startPos(),
1661 fEndLine.endPos(),
1662 SkVector::Make(offsetX, fHeight),
1663 SkVector::Make(fEndLine.width(), lineHeight),
1664 fEndLine.metrics(),
1665 needEllipsis,
1666 offsetX,
1667 fNoIndentWidth);
1668
1669 updateIntrinsicWidths();
1670 }
1671
1672 void TextWrapper::updateIntrinsicWidths() {
1673 fSoftLineMaxIntrinsicWidth += fCurrentLineWidthWithSpaces;
1674 fMaxIntrinsicWidth = std::max(fMaxIntrinsicWidth, fSoftLineMaxIntrinsicWidth);
1675 if (fHardLineBreak) {
1676 fSoftLineMaxIntrinsicWidth = 0;
1677 }
1678 }
1679
1680 bool TextWrapper::shouldBreakFormattingLoop() {
1681 if (fFormattingContext.hasEllipsis && fFormattingContext.unlimitedLines) {
1682 if (!fHardLineBreak) {
1683 return true;
1684 }
1685 } else if (isLastLine()) {
1686 fHardLineBreak = false;
1687 return true;
1688 }
1689 return false;
1690 }
1691
1692 bool TextWrapper::isLastLine() const { return fLineNumber >= fFormattingContext.maxLines; }
1693
1694 void TextWrapper::advanceToNextLine() {
1695 prepareForNextLine();
1696 fFirstLine = false;
1697 }
1698
1699 void TextWrapper::prepareForNextLine() {
1700 fHeight += calculateLineHeight();
1701 if (!fHardLineBreak || fCurrentStartLine != fEnd) {
1702 fEndLine.clean();
1703 }
1704 fEndLine.startFrom(fCurrentStartLine, fCurrentStartPos);
1705 fParent->fMaxWidthWithTrailingSpaces =
1706 std::max(fParent->fMaxWidthWithTrailingSpaces, fCurrentLineWidthWithSpaces);
1707 }
1708
1709 void TextWrapper::processRemainingClusters() {
1710 if (fEndLine.endCluster() == nullptr) {
1711 return;
1712 }
1713
1714 float lastWordLength = 0.0f;
1715 auto cluster = fEndLine.endCluster();
1716
1717 while (cluster != fEnd || cluster->endPos() < fEnd->endPos()) {
1718 fExceededMaxLines = true;
1719
1720 if (cluster->isHardBreak()) {
1721 handleHardBreak(lastWordLength);
1722 } else if (cluster->isWhitespaceBreak()) {
1723 handleWhitespaceBreak(cluster, lastWordLength);
1724 } else if (cluster->run().isPlaceholder()) {
1725 handlePlaceholder(cluster, lastWordLength);
1726 } else {
1727 handleRegularCluster(cluster, lastWordLength);
1728 }
1729
1730 ++cluster;
1731 }
1732
1733 fMinIntrinsicWidth = std::max(fMinIntrinsicWidth, lastWordLength);
1734 fMaxIntrinsicWidth = std::max(fMaxIntrinsicWidth, fSoftLineMaxIntrinsicWidth);
1735
1736 if (fParent->lines().empty()) {
1737 adjustMetricsForEmptyParagraph();
1738 fHeight = std::max(fHeight, fEndLine.metrics().height());
1739 }
1740 }
1741
1742 void TextWrapper::handleHardBreak(float& lastWordLength) {
1743 fMaxIntrinsicWidth = std::max(fMaxIntrinsicWidth, fSoftLineMaxIntrinsicWidth);
1744 fSoftLineMaxIntrinsicWidth = 0;
1745 fMinIntrinsicWidth = std::max(fMinIntrinsicWidth, lastWordLength);
1746 lastWordLength = 0;
1747 }
1748
1749 void TextWrapper::handleWhitespaceBreak(Cluster* cluster, float& lastWordLength) {
1750 fSoftLineMaxIntrinsicWidth += cluster->width();
1751 fMinIntrinsicWidth = std::max(fMinIntrinsicWidth, lastWordLength);
1752 lastWordLength = 0;
1753 }
1754
1755 void TextWrapper::handlePlaceholder(Cluster* cluster, float& lastWordLength) {
1756 fMinIntrinsicWidth = std::max(fMinIntrinsicWidth, lastWordLength);
1757 fSoftLineMaxIntrinsicWidth += cluster->width();
1758 fMinIntrinsicWidth = std::max(fMinIntrinsicWidth, cluster->width());
1759 lastWordLength = 0;
1760 }
1761
1762 void TextWrapper::handleRegularCluster(Cluster* cluster, float& lastWordLength) {
1763 fSoftLineMaxIntrinsicWidth += cluster->width();
1764 lastWordLength += cluster->width();
1765 }
1766
1767 void TextWrapper::adjustMetricsForEmptyParagraph() {
1768 if (fFormattingContext.disableFirstAscent) {
1769 fEndLine.metrics().fAscent = fEndLine.metrics().fRawAscent;
1770 }
1771 if (fFormattingContext.disableLastDescent && !fHardLineBreak) {
1772 fEndLine.metrics().fDescent = fEndLine.metrics().fRawDescent;
1773 }
1774 }
1775
1776 void TextWrapper::addFinalLineBreakIfNeeded(const AddLineToParagraph& addLine) {
1777 if (!fHardLineBreak) {
1778 return;
1779 }
1780
1781 if (fFormattingContext.disableLastDescent) {
1782 fEndLine.metrics().fDescent = fEndLine.metrics().fRawDescent;
1783 }
1784
1785 if (fParent->strutEnabled()) {
1786 fParent->strutMetrics().updateLineMetrics(fEndLine.metrics());
1787 }
1788
1789 ClusterRange clusters(fEndLine.breakCluster() - fStart, fEndLine.endCluster() - fStart);
1790
1791 addLine(fEndLine.breakCluster()->textRange(),
1792 fEndLine.breakCluster()->textRange(),
1793 fEndLine.endCluster()->textRange(),
1794 clusters,
1795 clusters,
1796 0,
1797 0,
1798 0,
1799 SkVector::Make(0, fHeight),
1800 SkVector::Make(0, fEndLine.metrics().height()),
1801 fEndLine.metrics(),
1802 false,
1803 fParent->detectIndents(fLineNumber - 1),
1804 fNoIndentWidth);
1805
1806 fHeight += fEndLine.metrics().height();
1807 fParent->lines().back().setMaxRunMetrics(fMaxRunMetrics);
1808 }
1809
1810 void TextWrapper::adjustFirstLastLineMetrics() {
1811 if (fParent->lines().empty()) {
1812 return;
1813 }
1814
1815 if (fFormattingContext.disableFirstAscent) {
1816 fParent->lines().front().setAscentStyle(LineMetricStyle::Typographic);
1817 }
1818
1819 if (fFormattingContext.disableLastDescent) {
1820 fParent->lines().back().setDescentStyle(LineMetricStyle::Typographic);
1821 }
1822 }
1823
1824 void TextWrapper::preProcessingForLineStretches() {
1825 if (fLineStretches.empty()) {
1826 return;
1827 }
1828
1829 const ParagraphStyle& style = fParent->getParagraphStyle();
1830 EllipsisModal ellipsisMod = style.getEllipsisMod();
1831 if (style.getMaxLines() == 1 && fLineStretches.size() > 1 &&
1832 (ellipsisMod == EllipsisModal::HEAD || ellipsisMod == EllipsisModal::MIDDLE)) {
1833 TextStretch merged = fLineStretches.front();
1834
1835 for (size_t i = 1; i < fLineStretches.size(); ++i) {
1836 merged.extend(fLineStretches[i]);
1837 }
1838 fLineStretches.clear();
1839 fLineStretches.push_back(merged);
1840 }
1841 }
1842
1843 void TextWrapper::layoutLinesBalanced(ParagraphImpl* parent,
1844 SkScalar maxWidth,
1845 const AddLineToParagraph& addLine) {
1846 reset();
1847 SkSpan<Cluster> span = parent->clusters();
1848 if (span.empty()) {
1849 return;
1850 }
1851
1852 // Generate word stretches
1853 generateWordStretches(span, parent->getWordBreakType());
1854
1855 for (std::vector<TextStretch>& wordStretches : fWordStretchesBatch) {
1856 // Trimming a word that is longer than the line width
1857 formalizedClusters(wordStretches, maxWidth);
1858
1859 std::vector<SkScalar> clustersWidthVector = generateWordsWidthInfo(wordStretches);
1860
1861 // Execute the balancing algorithm to generate line grouping information
1862 std::vector<std::pair<size_t, size_t>> linesGroupInfo =
1863 generateLinesGroupInfo(clustersWidthVector, maxWidth);
1864
1865 generateLineStretches(linesGroupInfo, wordStretches);
1866 }
1867
1868 preProcessingForLineStretches();
1869
1870 generateTextLines(maxWidth, addLine, span);
1871 }
1872 #else
1873 // Since we allow cluster clipping when they don't fit
1874 // we have to work with stretches - parts of clusters
1875 void TextWrapper::lookAhead(SkScalar maxWidth, Cluster* endOfClusters, bool applyRoundingHack) {
1876 reset();
1877 fEndLine.metrics().clean();
1878 fWords.startFrom(fEndLine.startCluster(), fEndLine.startPos());
1879 fClusters.startFrom(fEndLine.startCluster(), fEndLine.startPos());
1880 fClip.startFrom(fEndLine.startCluster(), fEndLine.startPos());
1881 LineBreakerWithLittleRounding breaker(maxWidth, applyRoundingHack);
1882 Cluster* nextNonBreakingSpace = nullptr;
1883 for (auto cluster = fEndLine.endCluster(); cluster < endOfClusters; ++cluster) {
1884 if (cluster->isHardBreak()) {
1885 } else if (
1886 SkScalar width = fWords.width() + fClusters.width() + cluster->width();
1887 breaker.breakLine(width)) {
1888 if (cluster->isWhitespaceBreak()) {
1889 // It's the end of the word
1890 fClusters.extend(cluster);
1891 fMinIntrinsicWidth = std::max(fMinIntrinsicWidth, this->getClustersTrimmedWidth());
1892 fWords.extend(fClusters);
1893 continue;
1894 } else if (cluster->run().isPlaceholder()) {
1895 if (!fClusters.empty()) {
1896 // Placeholder ends the previous word
1897 fMinIntrinsicWidth = std::max(fMinIntrinsicWidth, this->getClustersTrimmedWidth());
1898 fWords.extend(fClusters);
1899 }
1900 if (cluster->width() > maxWidth && fWords.empty()) {
1901 // Placeholder is the only text and it's longer than the line;
1902 // it does not count in fMinIntrinsicWidth
1903 fClusters.extend(cluster);
1904 fTooLongCluster = true;
1905 fTooLongWord = true;
1906 } else {
1907 // Placeholder does not fit the line; it will be considered again on the next line
1908 }
1909 break;
1910 }
1911 // Walk further to see if there is a too long word, cluster or glyph
1912 SkScalar nextWordLength = fClusters.width();
1913 SkScalar nextShortWordLength = nextWordLength;
1914 for (auto further = cluster; further != endOfClusters; ++further) {
1915 if (further->isSoftBreak() || further->isHardBreak() || further->isWhitespaceBreak()) {
1916 break;
1917 }
1918 if (further->run().isPlaceholder()) {
1919 // Placeholder ends the word
1920 break;
1921 }
1922 if (nextWordLength > 0 && nextWordLength <= maxWidth && further->isIntraWordBreak()) {
1923 // The cluster is spaces but not the end of the word in a normal sense
1924 nextNonBreakingSpace = further;
1925 nextShortWordLength = nextWordLength;
1926 }
1927 if (maxWidth == 0) {
1928 // This is a tricky flutter case: layout(width:0) places 1 cluster on each line
1929 nextWordLength = std::max(nextWordLength, further->width());
1930 } else {
1931 nextWordLength += further->width();
1932 }
1933 }
1934 if (nextWordLength > maxWidth) {
1935 if (nextNonBreakingSpace != nullptr) {
1936 // We only get here if the non-breaking space improves our situation
1937 // (allows us to break the text to fit the word)
1938 if (SkScalar shortLength = fWords.width() + nextShortWordLength;
1939 !breaker.breakLine(shortLength)) {
1940 // We can add the short word to the existing line
1941 fClusters = TextStretch(fClusters.startCluster(), nextNonBreakingSpace,
1942 fClusters.metrics().getForceStrut());
1943 fMinIntrinsicWidth = std::max(fMinIntrinsicWidth, nextShortWordLength);
1944 fWords.extend(fClusters);
1945 } else {
1946 // We can place the short word on the next line
1947 fClusters.clean();
1948 }
1949 // Either way we are not in "word is too long" situation anymore
1950 break;
1951 }
1952 // If the word is too long we can break it right now and hope it's enough
1953 fMinIntrinsicWidth = std::max(fMinIntrinsicWidth, nextWordLength);
1954 if (fClusters.endPos() - fClusters.startPos() > 1 ||
1955 fWords.empty()) {
1956 fTooLongWord = true;
1957 } else {
1958 // Even if the word is too long there is a very little space on this line.
1959 // let's deal with it on the next line.
1960 }
1961 }
1962 if (cluster->width() > maxWidth) {
1963 fClusters.extend(cluster);
1964 fTooLongCluster = true;
1965 fTooLongWord = true;
1966 }
1967 break;
1968 }
1969 if (cluster->run().isPlaceholder()) {
1970 if (!fClusters.empty()) {
1971 // Placeholder ends the previous word (placeholders are ignored in trimming)
1972 fMinIntrinsicWidth = std::max(fMinIntrinsicWidth, getClustersTrimmedWidth());
1973 fWords.extend(fClusters);
1974 }
1975 // Placeholder is separate word and its width now is counted in minIntrinsicWidth
1976 fMinIntrinsicWidth = std::max(fMinIntrinsicWidth, cluster->width());
1977 fWords.extend(cluster);
1978 } else {
1979 fClusters.extend(cluster);
1980 // Keep adding clusters/words
1981 if (fClusters.endOfWord()) {
1982 fMinIntrinsicWidth = std::max(fMinIntrinsicWidth, getClustersTrimmedWidth());
1983 fWords.extend(fClusters);
1984 }
1985 }
1986 if ((fHardLineBreak = cluster->isHardBreak())) {
1987 // Stop at the hard line break
1988 break;
1989 }
1990 }
1991 }
1992 void TextWrapper::moveForward(bool hasEllipsis) {
1993 // We normally break lines by words.
1994 // The only way we may go to clusters is if the word is too long or
1995 // it's the first word and it has an ellipsis attached to it.
1996 // If nothing fits we show the clipping.
1997 if (!fWords.empty()) {
1998 fEndLine.extend(fWords);
1999 #ifdef SK_IGNORE_SKPARAGRAPH_ELLIPSIS_FIX
2000 if (!fTooLongWord || hasEllipsis) { // Ellipsis added to a word
2001 #else
2002 if (!fTooLongWord && !hasEllipsis) { // Ellipsis added to a grapheme
2003 #endif
2004 return;
2005 }
2006 }
2007 if (!fClusters.empty()) {
2008 fEndLine.extend(fClusters);
2009 if (!fTooLongCluster) {
2010 return;
2011 }
2012 }
2013 if (!fClip.empty()) {
2014 // Flutter: forget the clipped cluster but keep the metrics
2015 fEndLine.metrics().add(fClip.metrics());
2016 }
2017 }
2018 // Special case for start/end cluster since they can be clipped
2019 void TextWrapper::trimEndSpaces(TextAlign align) {
2020 // Remember the breaking position
2021 fEndLine.saveBreak();
2022 // Skip all space cluster at the end
2023 for (auto cluster = fEndLine.endCluster();
2024 cluster >= fEndLine.startCluster() && cluster->isWhitespaceBreak();
2025 --cluster) {
2026 fEndLine.trim(cluster);
2027 }
2028 fEndLine.trim();
2029 }
2030 SkScalar TextWrapper::getClustersTrimmedWidth() {
2031 // Move the end of the line to the left
2032 SkScalar width = 0;
2033 bool trailingSpaces = true;
2034 for (auto cluster = fClusters.endCluster(); cluster >= fClusters.startCluster(); --cluster) {
2035 if (cluster->run().isPlaceholder()) {
2036 continue;
2037 }
2038 if (trailingSpaces) {
2039 if (!cluster->isWhitespaceBreak()) {
2040 width += cluster->trimmedWidth(cluster->endPos());
2041 trailingSpaces = false;
2042 }
2043 continue;
2044 }
2045 width += cluster->width();
2046 }
2047 return width;
2048 }
2049 // Trim the beginning spaces in case of soft line break
2050 std::tuple<Cluster*, size_t, SkScalar> TextWrapper::trimStartSpaces(Cluster* endOfClusters) {
2051 if (fHardLineBreak) {
2052 // End of line is always end of cluster, but need to skip \n
2053 auto width = fEndLine.width();
2054 auto cluster = fEndLine.endCluster() + 1;
2055 while (cluster < fEndLine.breakCluster() && cluster->isWhitespaceBreak()) {
2056 width += cluster->width();
2057 ++cluster;
2058 }
2059 return std::make_tuple(fEndLine.breakCluster() + 1, 0, width);
2060 }
2061 // breakCluster points to the end of the line;
2062 // It's a soft line break so we need to move lineStart forward skipping all the spaces
2063 auto width = fEndLine.widthWithGhostSpaces();
2064 auto cluster = fEndLine.breakCluster() + 1;
2065 while (cluster < endOfClusters && cluster->isWhitespaceBreak()) {
2066 width += cluster->width();
2067 ++cluster;
2068 }
2069 if (fEndLine.breakCluster()->isWhitespaceBreak() && fEndLine.breakCluster() < endOfClusters) {
2070 // In case of a soft line break by the whitespace
2071 // fBreak should point to the beginning of the next line
2072 // (it only matters when there are trailing spaces)
2073 fEndLine.shiftBreak();
2074 }
2075 return std::make_tuple(cluster, 0, width);
2076 }
2077 void TextWrapper::breakTextIntoLines(ParagraphImpl* parent,
2078 SkScalar maxWidth,
2079 const AddLineToParagraph& addLine) {
2080 fHeight = 0;
2081 fMinIntrinsicWidth = std::numeric_limits<SkScalar>::min();
2082 fMaxIntrinsicWidth = std::numeric_limits<SkScalar>::min();
2083 auto span = parent->clusters();
2084 if (span.empty()) {
2085 return;
2086 }
2087 auto maxLines = parent->paragraphStyle().getMaxLines();
2088 auto align = parent->paragraphStyle().effective_align();
2089 auto unlimitedLines = maxLines == std::numeric_limits<size_t>::max();
2090 auto endlessLine = !SkScalarIsFinite(maxWidth);
2091 auto hasEllipsis = parent->paragraphStyle().ellipsized();
2092 auto disableFirstAscent = parent->paragraphStyle().getTextHeightBehavior() & TextHeightBehavior::kDisableFirstAscent;
2093 auto disableLastDescent = parent->paragraphStyle().getTextHeightBehavior() & TextHeightBehavior::kDisableLastDescent;
2094 bool firstLine = true; // We only interested in fist line if we have to disable the first ascent
2095 SkScalar softLineMaxIntrinsicWidth = 0;
2096 fEndLine = TextStretch(span.begin(), span.begin(), parent->strutForceHeight());
2097 auto end = span.end() - 1;
2098 auto start = span.begin();
2099 InternalLineMetrics maxRunMetrics;
2100 bool needEllipsis = false;
2101 while (fEndLine.endCluster() != end) {
2102 this->lookAhead(maxWidth, end, parent->getApplyRoundingHack());
2103 auto lastLine = (hasEllipsis && unlimitedLines) || fLineNumber >= maxLines;
2104 needEllipsis = hasEllipsis && !endlessLine && lastLine;
2105 this->moveForward(needEllipsis);
2106 needEllipsis &= fEndLine.endCluster() < end - 1; // Only if we have some text to ellipsize
2107 // Do not trim end spaces on the naturally last line of the left aligned text
2108 this->trimEndSpaces(align);
2109 // For soft line breaks add to the line all the spaces next to it
2110 Cluster* startLine;
2111 size_t pos;
2112 SkScalar widthWithSpaces;
2113 std::tie(startLine, pos, widthWithSpaces) = this->trimStartSpaces(end);
2114 if (needEllipsis && !fHardLineBreak) {
2115 // This is what we need to do to preserve a space before the ellipsis
2116 fEndLine.restoreBreak();
2117 widthWithSpaces = fEndLine.widthWithGhostSpaces();
2118 }
2119 // If the line is empty with the hard line break, let's take the paragraph font (flutter???)
2120 if (fEndLine.metrics().isClean()) {
2121 fEndLine.setMetrics(parent->getEmptyMetrics());
2122 }
2123 // Deal with placeholder clusters == runs[@size==1]
2124 Run* lastRun = nullptr;
2125 for (auto cluster = fEndLine.startCluster(); cluster <= fEndLine.endCluster(); ++cluster) {
2126 auto r = cluster->runOrNull();
2127 if (r == lastRun) {
2128 continue;
2129 }
2130 lastRun = r;
2131 if (lastRun->placeholderStyle() != nullptr) {
2132 SkASSERT(lastRun->size() == 1);
2133 // Update the placeholder metrics so we can get the placeholder positions later
2134 // and the line metrics (to make sure the placeholder fits)
2135 lastRun->updateMetrics(&fEndLine.metrics());
2136 }
2137 }
2138 // Before we update the line metrics with struts,
2139 // let's save it for GetRectsForRange(RectHeightStyle::kMax)
2140 maxRunMetrics = fEndLine.metrics();
2141 maxRunMetrics.fForceStrut = false;
2142 TextRange textExcludingSpaces(fEndLine.startCluster()->textRange().start, fEndLine.endCluster()->textRange().end);
2143 TextRange text(fEndLine.startCluster()->textRange().start, fEndLine.breakCluster()->textRange().start);
2144 TextRange textIncludingNewlines(fEndLine.startCluster()->textRange().start, startLine->textRange().start);
2145 if (startLine == end) {
2146 textIncludingNewlines.end = parent->text().size();
2147 text.end = parent->text().size();
2148 }
2149 ClusterRange clusters(fEndLine.startCluster() - start, fEndLine.endCluster() - start + 1);
2150 ClusterRange clustersWithGhosts(fEndLine.startCluster() - start, startLine - start);
2151 if (disableFirstAscent && firstLine) {
2152 fEndLine.metrics().fAscent = fEndLine.metrics().fRawAscent;
2153 }
2154 if (disableLastDescent && (lastLine || (startLine == end && !fHardLineBreak ))) {
2155 fEndLine.metrics().fDescent = fEndLine.metrics().fRawDescent;
2156 }
2157 if (parent->strutEnabled()) {
2158 // Make sure font metrics are not less than the strut
2159 parent->strutMetrics().updateLineMetrics(fEndLine.metrics());
2160 }
2161 SkScalar lineHeight = fEndLine.metrics().height();
2162 firstLine = false;
2163 if (fEndLine.empty()) {
2164 // Correct text and clusters (make it empty for an empty line)
2165 textExcludingSpaces.end = textExcludingSpaces.start;
2166 clusters.end = clusters.start;
2167 }
2168 // In case of a force wrapping we don't have a break cluster and have to use the end cluster
2169 text.end = std::max(text.end, textExcludingSpaces.end);
2170 addLine(textExcludingSpaces,
2171 text,
2172 textIncludingNewlines, clusters, clustersWithGhosts, widthWithSpaces,
2173 fEndLine.startPos(),
2174 fEndLine.endPos(),
2175 SkVector::Make(0, fHeight),
2176 SkVector::Make(fEndLine.width(), lineHeight),
2177 fEndLine.metrics(),
2178 needEllipsis && !fHardLineBreak);
2179 softLineMaxIntrinsicWidth += widthWithSpaces;
2180 fMaxIntrinsicWidth = std::max(fMaxIntrinsicWidth, softLineMaxIntrinsicWidth);
2181 if (fHardLineBreak) {
2182 softLineMaxIntrinsicWidth = 0;
2183 }
2184 // Start a new line
2185 fHeight += lineHeight;
2186 if (!fHardLineBreak || startLine != end) {
2187 fEndLine.clean();
2188 }
2189 fEndLine.startFrom(startLine, pos);
2190 parent->fMaxWidthWithTrailingSpaces = std::max(parent->fMaxWidthWithTrailingSpaces, widthWithSpaces);
2191 if (hasEllipsis && unlimitedLines) {
2192 // There is one case when we need an ellipsis on a separate line
2193 // after a line break when width is infinite
2194 if (!fHardLineBreak) {
2195 break;
2196 }
2197 } else if (lastLine) {
2198 // There is nothing more to draw
2199 fHardLineBreak = false;
2200 break;
2201 }
2202 ++fLineNumber;
2203 }
2204 // We finished formatting the text but we need to scan the rest for some numbers
2205 if (fEndLine.endCluster() != nullptr) {
2206 auto lastWordLength = 0.0f;
2207 auto cluster = fEndLine.endCluster();
2208 while (cluster != end || cluster->endPos() < end->endPos()) {
2209 fExceededMaxLines = true;
2210 if (cluster->isHardBreak()) {
2211 // Hard line break ends the word and the line
2212 fMaxIntrinsicWidth = std::max(fMaxIntrinsicWidth, softLineMaxIntrinsicWidth);
2213 softLineMaxIntrinsicWidth = 0;
2214 fMinIntrinsicWidth = std::max(fMinIntrinsicWidth, lastWordLength);
2215 lastWordLength = 0;
2216 } else if (cluster->isWhitespaceBreak()) {
2217 // Whitespaces end the word
2218 softLineMaxIntrinsicWidth += cluster->width();
2219 fMinIntrinsicWidth = std::max(fMinIntrinsicWidth, lastWordLength);
2220 lastWordLength = 0;
2221 } else if (cluster->run().isPlaceholder()) {
2222 // Placeholder ends the previous word and creates a separate one
2223 fMinIntrinsicWidth = std::max(fMinIntrinsicWidth, lastWordLength);
2224 // Placeholder width now counts in fMinIntrinsicWidth
2225 softLineMaxIntrinsicWidth += cluster->width();
2226 fMinIntrinsicWidth = std::max(fMinIntrinsicWidth, cluster->width());
2227 lastWordLength = 0;
2228 } else {
2229 // Nothing out of ordinary - just add this cluster to the word and to the line
2230 softLineMaxIntrinsicWidth += cluster->width();
2231 lastWordLength += cluster->width();
2232 }
2233 ++cluster;
2234 }
2235 fMinIntrinsicWidth = std::max(fMinIntrinsicWidth, lastWordLength);
2236 fMaxIntrinsicWidth = std::max(fMaxIntrinsicWidth, softLineMaxIntrinsicWidth);
2237 if (parent->lines().empty()) {
2238 // In case we could not place even a single cluster on the line
2239 if (disableFirstAscent) {
2240 fEndLine.metrics().fAscent = fEndLine.metrics().fRawAscent;
2241 }
2242 if (disableLastDescent && !fHardLineBreak) {
2243 fEndLine.metrics().fDescent = fEndLine.metrics().fRawDescent;
2244 }
2245 fHeight = std::max(fHeight, fEndLine.metrics().height());
2246 }
2247 }
2248 if (fHardLineBreak) {
2249 if (disableLastDescent) {
2250 fEndLine.metrics().fDescent = fEndLine.metrics().fRawDescent;
2251 }
2252 // Last character is a line break
2253 if (parent->strutEnabled()) {
2254 // Make sure font metrics are not less than the strut
2255 parent->strutMetrics().updateLineMetrics(fEndLine.metrics());
2256 }
2257 ClusterRange clusters(fEndLine.breakCluster() - start, fEndLine.endCluster() - start);
2258 addLine(fEndLine.breakCluster()->textRange(),
2259 fEndLine.breakCluster()->textRange(),
2260 fEndLine.endCluster()->textRange(),
2261 clusters,
2262 clusters,
2263 0,
2264 0,
2265 0,
2266 SkVector::Make(0, fHeight),
2267 SkVector::Make(0, fEndLine.metrics().height()),
2268 fEndLine.metrics(),
2269 needEllipsis);
2270 fHeight += fEndLine.metrics().height();
2271 parent->lines().back().setMaxRunMetrics(maxRunMetrics);
2272 }
2273 if (parent->lines().empty()) {
2274 return;
2275 }
2276 // Correct line metric styles for the first and for the last lines if needed
2277 if (disableFirstAscent) {
2278 parent->lines().front().setAscentStyle(LineMetricStyle::Typographic);
2279 }
2280 if (disableLastDescent) {
2281 parent->lines().back().setDescentStyle(LineMetricStyle::Typographic);
2282 }
2283 }
2284 #endif
2285 } // namespace textlayout
2286 } // namespace skia
2287