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