| 823 | } |
| 824 | |
| 825 | void ParsedText::extractLine(const size_t breakIndex, const int pageWidth, const std::vector<uint16_t>& wordWidths, |
| 826 | const std::vector<bool>& continuesVec, const std::vector<bool>& noSpaceBeforeVec, |
| 827 | const std::vector<size_t>& lineBreakIndices, |
| 828 | const std::function<void(std::shared_ptr<TextBlock>)>& processLine, |
| 829 | const GfxRenderer& renderer, const int fontId) { |
| 830 | const size_t lineBreak = lineBreakIndices[breakIndex]; |
| 831 | const size_t lastBreakAt = breakIndex > 0 ? lineBreakIndices[breakIndex - 1] : 0; |
| 832 | const size_t lineWordCount = lineBreak - lastBreakAt; |
| 833 | |
| 834 | const int firstLineIndent = resolveFirstLineIndent(breakIndex == 0, renderer, fontId); |
| 835 | |
| 836 | // Build line data by moving from the original vectors using index range |
| 837 | std::vector<std::string> lineWords; |
| 838 | lineWords.reserve(lineWordCount); |
| 839 | std::vector<EpdFontFamily::Style> lineWordStyles; |
| 840 | lineWordStyles.reserve(lineWordCount); |
| 841 | |
| 842 | for (size_t i = 0; i < lineWordCount; ++i) { |
| 843 | std::string word = std::move(words[lastBreakAt + i]); |
| 844 | if (containsSoftHyphen(word)) { |
| 845 | stripSoftHyphensInPlace(word); |
| 846 | } |
| 847 | lineWords.push_back(std::move(word)); |
| 848 | lineWordStyles.push_back(wordStyles[lastBreakAt + i]); |
| 849 | } |
| 850 | |
| 851 | // Calculate total word width for this line, count actual word gaps, |
| 852 | // and accumulate total natural gap widths (including space kerning adjustments). |
| 853 | int lineWordWidthSum = 0; |
| 854 | size_t actualGapCount = 0; |
| 855 | int totalNaturalGaps = 0; |
| 856 | |
| 857 | for (size_t wordIdx = 0; wordIdx < lineWordCount; wordIdx++) { |
| 858 | lineWordWidthSum += wordWidths[lastBreakAt + wordIdx]; |
| 859 | // Count gaps: each word after the first creates a gap, unless it's a continuation |
| 860 | if (wordIdx > 0 && noSpaceBeforeVec[lastBreakAt + wordIdx]) { |
| 861 | // Unicode break opportunity with no inserted Latin-style space. It is still |
| 862 | // a stretchable gap for justified CJK/Korean text. |
| 863 | actualGapCount++; |
| 864 | } else if (wordIdx > 0 && !continuesVec[lastBreakAt + wordIdx]) { |
| 865 | actualGapCount++; |
| 866 | totalNaturalGaps += renderer.getSpaceAdvance(fontId, lastCodepoint(lineWords[wordIdx - 1]), |
| 867 | firstCodepoint(lineWords[wordIdx]), lineWordStyles[wordIdx - 1]); |
| 868 | } else if (wordIdx > 0 && continuesVec[lastBreakAt + wordIdx]) { |
| 869 | // Non-breaking space tokens (" " with continues=true) are visible, stretchable spaces — |
| 870 | // count them as justifiable gaps so justifyExtra is distributed to them too. |
| 871 | if (lineWords[wordIdx] == " ") { |
| 872 | actualGapCount++; |
| 873 | } |
| 874 | // Cross-boundary kerning for continuation words (e.g. nonbreaking spaces, attached punctuation) |
| 875 | totalNaturalGaps += renderer.getKerning(fontId, lastCodepoint(lineWords[wordIdx - 1]), |
| 876 | firstCodepoint(lineWords[wordIdx]), lineWordStyles[wordIdx - 1]); |
| 877 | } |
| 878 | } |
| 879 | |
| 880 | // Calculate spacing (account for indent reducing effective page width on first line) |
| 881 | const int effectivePageWidth = pageWidth - firstLineIndent; |
| 882 | const bool isLastLine = breakIndex == lineBreakIndices.size() - 1; |
nothing calls this directly
no test coverage detected