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