| 545 | } |
| 546 | |
| 547 | std::vector<size_t> ParsedText::computeLineBreaks(const GfxRenderer& renderer, const int fontId, const int pageWidth, |
| 548 | std::vector<uint16_t>& wordWidths, std::vector<bool>& continuesVec, |
| 549 | std::vector<bool>& noSpaceBeforeVec) { |
| 550 | if (words.empty()) { |
| 551 | return {}; |
| 552 | } |
| 553 | |
| 554 | const int firstLineIndent = resolveFirstLineIndent(true, renderer, fontId); |
| 555 | |
| 556 | // Ensure any word that would overflow even as the first entry on a line is split using fallback hyphenation. |
| 557 | for (size_t i = 0; i < wordWidths.size(); ++i) { |
| 558 | // First word needs to fit in reduced width if there's an indent |
| 559 | const int effectiveWidth = i == 0 ? pageWidth - firstLineIndent : pageWidth; |
| 560 | while (wordWidths[i] > effectiveWidth) { |
| 561 | if (!hyphenateWordAtIndex(i, effectiveWidth, renderer, fontId, wordWidths, /*allowFallbackBreaks=*/true)) { |
| 562 | break; |
| 563 | } |
| 564 | } |
| 565 | } |
| 566 | |
| 567 | const size_t totalWordCount = words.size(); |
| 568 | |
| 569 | // DP table to store the minimum badness (cost) of lines starting at index i |
| 570 | std::vector<int> dp(totalWordCount); |
| 571 | // 'ans[i]' stores the index 'j' of the *last word* in the optimal line starting at 'i' |
| 572 | std::vector<size_t> ans(totalWordCount); |
| 573 | |
| 574 | // Base Case |
| 575 | dp[totalWordCount - 1] = 0; |
| 576 | ans[totalWordCount - 1] = totalWordCount - 1; |
| 577 | |
| 578 | for (int i = totalWordCount - 2; i >= 0; --i) { |
| 579 | int currlen = 0; |
| 580 | dp[i] = MAX_COST; |
| 581 | |
| 582 | // First line has reduced width due to text-indent |
| 583 | const int effectivePageWidth = i == 0 ? pageWidth - firstLineIndent : pageWidth; |
| 584 | |
| 585 | for (size_t j = i; j < totalWordCount; ++j) { |
| 586 | // Add space before word j, unless it's the first word on the line or a continuation |
| 587 | int gap = 0; |
| 588 | if (j > static_cast<size_t>(i) && noSpaceBeforeVec[j]) { |
| 589 | gap = 0; |
| 590 | } else if (j > static_cast<size_t>(i) && !continuesVec[j]) { |
| 591 | gap = |
| 592 | renderer.getSpaceAdvance(fontId, lastCodepoint(words[j - 1]), firstCodepoint(words[j]), wordStyles[j - 1]); |
| 593 | } else if (j > static_cast<size_t>(i) && continuesVec[j]) { |
| 594 | // Cross-boundary kerning for continuation words (e.g. nonbreaking spaces, attached punctuation) |
| 595 | gap = renderer.getKerning(fontId, lastCodepoint(words[j - 1]), firstCodepoint(words[j]), wordStyles[j - 1]); |
| 596 | } |
| 597 | currlen += wordWidths[j] + gap; |
| 598 | |
| 599 | if (currlen > effectivePageWidth) { |
| 600 | break; |
| 601 | } |
| 602 | |
| 603 | // Cannot break after word j if the next word attaches to it (continuation group) |
| 604 | if (j + 1 < totalWordCount && continuesVec[j + 1]) { |
nothing calls this directly
no test coverage detected