Address review comment: also suppress CSS styling on continuation
This commit is contained in:
@@ -246,6 +246,15 @@ void ParsedText::layoutAndExtractLines(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const int pageWidth = viewportWidth;
|
const int pageWidth = viewportWidth;
|
||||||
|
|
||||||
|
// Compute firstLineIndent once here so all layout helpers use the same value.
|
||||||
|
// On a continuation flush the remaining words are mid-paragraph, so no indent.
|
||||||
|
const int firstLineIndent =
|
||||||
|
!isContinuation_ && blockStyle.textIndentDefined &&
|
||||||
|
(blockStyle.alignment == CssTextAlign::Justify || blockStyle.alignment == CssTextAlign::Left)
|
||||||
|
? std::min(std::max<int>(static_cast<int>(blockStyle.textIndent), -(pageWidth - 1)), pageWidth - 1)
|
||||||
|
: 0;
|
||||||
|
|
||||||
auto wordWidths = calculateWordWidths(renderer, fontId);
|
auto wordWidths = calculateWordWidths(renderer, fontId);
|
||||||
|
|
||||||
std::vector<size_t> lineBreakIndices;
|
std::vector<size_t> lineBreakIndices;
|
||||||
@@ -256,9 +265,9 @@ void ParsedText::layoutAndExtractLines(
|
|||||||
// Use greedy layout that can split words mid-loop when a hyphenated prefix fits.
|
// Use greedy layout that can split words mid-loop when a hyphenated prefix fits.
|
||||||
lineBreakIndices =
|
lineBreakIndices =
|
||||||
computeHyphenatedLineBreaks(renderer, fontId, pageWidth, wordWidths, wordContinues, lineEndsWithHyphenatedWord,
|
computeHyphenatedLineBreaks(renderer, fontId, pageWidth, wordWidths, wordContinues, lineEndsWithHyphenatedWord,
|
||||||
splitPrefixWordIndexes, splitInsertedHyphen);
|
splitPrefixWordIndexes, splitInsertedHyphen, firstLineIndent);
|
||||||
} else {
|
} else {
|
||||||
lineBreakIndices = computeLineBreaks(renderer, fontId, pageWidth, wordWidths, wordContinues);
|
lineBreakIndices = computeLineBreaks(renderer, fontId, pageWidth, wordWidths, wordContinues, firstLineIndent);
|
||||||
lineEndsWithHyphenatedWord.assign(lineBreakIndices.size(), false);
|
lineEndsWithHyphenatedWord.assign(lineBreakIndices.size(), false);
|
||||||
splitPrefixWordIndexes.assign(lineBreakIndices.size(), -1);
|
splitPrefixWordIndexes.assign(lineBreakIndices.size(), -1);
|
||||||
splitInsertedHyphen.assign(lineBreakIndices.size(), false);
|
splitInsertedHyphen.assign(lineBreakIndices.size(), false);
|
||||||
@@ -268,7 +277,7 @@ void ParsedText::layoutAndExtractLines(
|
|||||||
for (size_t i = 0; i < lineCount; ++i) {
|
for (size_t i = 0; i < lineCount; ++i) {
|
||||||
const bool lineEndedWithHyphenation = i < lineEndsWithHyphenatedWord.size() ? lineEndsWithHyphenatedWord[i] : false;
|
const bool lineEndedWithHyphenation = i < lineEndsWithHyphenatedWord.size() ? lineEndsWithHyphenatedWord[i] : false;
|
||||||
const auto result = extractLine(i, pageWidth, wordWidths, wordContinues, lineBreakIndices, processLine, renderer,
|
const auto result = extractLine(i, pageWidth, wordWidths, wordContinues, lineBreakIndices, processLine, renderer,
|
||||||
fontId, lineEndedWithHyphenation, false);
|
fontId, lineEndedWithHyphenation, false, firstLineIndent);
|
||||||
|
|
||||||
if (result == LineProcessResult::RetryWithoutHyphenation && lineEndedWithHyphenation) {
|
if (result == LineProcessResult::RetryWithoutHyphenation && lineEndedWithHyphenation) {
|
||||||
const size_t lineStart = i > 0 ? lineBreakIndices[i - 1] : 0;
|
const size_t lineStart = i > 0 ? lineBreakIndices[i - 1] : 0;
|
||||||
@@ -314,7 +323,8 @@ void ParsedText::layoutAndExtractLines(
|
|||||||
// Keep previous lines fixed; recompute only this specific line without hyphenation.
|
// Keep previous lines fixed; recompute only this specific line without hyphenation.
|
||||||
// Suppression is intentionally line-local.
|
// Suppression is intentionally line-local.
|
||||||
const size_t retryBreak =
|
const size_t retryBreak =
|
||||||
computeSingleLineBreakNoHyphen(renderer, fontId, pageWidth, wordWidths, wordContinues, lineStart);
|
computeSingleLineBreakNoHyphen(renderer, fontId, pageWidth, wordWidths, wordContinues, lineStart,
|
||||||
|
firstLineIndent);
|
||||||
|
|
||||||
lineBreakIndices.resize(i + 1);
|
lineBreakIndices.resize(i + 1);
|
||||||
lineEndsWithHyphenatedWord.resize(i + 1);
|
lineEndsWithHyphenatedWord.resize(i + 1);
|
||||||
@@ -334,7 +344,7 @@ void ParsedText::layoutAndExtractLines(
|
|||||||
LOG_DBG("PTX", "Rerendering line %u with hyphenation suppressed, retry attempt: %s", static_cast<unsigned>(i),
|
LOG_DBG("PTX", "Rerendering line %u with hyphenation suppressed, retry attempt: %s", static_cast<unsigned>(i),
|
||||||
retryPreview.c_str());
|
retryPreview.c_str());
|
||||||
extractLine(i, pageWidth, wordWidths, wordContinues, lineBreakIndices, processLine, renderer, fontId, false,
|
extractLine(i, pageWidth, wordWidths, wordContinues, lineBreakIndices, processLine, renderer, fontId, false,
|
||||||
true);
|
true, firstLineIndent);
|
||||||
|
|
||||||
// Resume regular hyphenation from the first word after the retried line.
|
// Resume regular hyphenation from the first word after the retried line.
|
||||||
const size_t resumeIndex = lineBreakIndices[i];
|
const size_t resumeIndex = lineBreakIndices[i];
|
||||||
@@ -387,20 +397,12 @@ std::vector<uint16_t> ParsedText::calculateWordWidths(const GfxRenderer& rendere
|
|||||||
}
|
}
|
||||||
|
|
||||||
std::vector<size_t> ParsedText::computeLineBreaks(const GfxRenderer& renderer, const int fontId, const int pageWidth,
|
std::vector<size_t> ParsedText::computeLineBreaks(const GfxRenderer& renderer, const int fontId, const int pageWidth,
|
||||||
std::vector<uint16_t>& wordWidths, std::vector<bool>& continuesVec) {
|
std::vector<uint16_t>& wordWidths, std::vector<bool>& continuesVec,
|
||||||
|
const int firstLineIndent) {
|
||||||
if (words.empty()) {
|
if (words.empty()) {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate first line indent (only for left/justified text).
|
|
||||||
// Explicit CSS text-indent always applies — author intent overrides the extraParagraphSpacing
|
|
||||||
// toggle. Only the implicit EmSpace fallback in applyParagraphIndent() is gated on it.
|
|
||||||
const int firstLineIndent =
|
|
||||||
blockStyle.textIndentDefined &&
|
|
||||||
(blockStyle.alignment == CssTextAlign::Justify || blockStyle.alignment == CssTextAlign::Left)
|
|
||||||
? std::min(std::max<int>(static_cast<int>(blockStyle.textIndent), -(pageWidth - 1)), pageWidth - 1)
|
|
||||||
: 0;
|
|
||||||
|
|
||||||
// Ensure any word that would overflow even as the first entry on a line is split using fallback hyphenation.
|
// Ensure any word that would overflow even as the first entry on a line is split using fallback hyphenation.
|
||||||
for (size_t i = 0; i < wordWidths.size(); ++i) {
|
for (size_t i = 0; i < wordWidths.size(); ++i) {
|
||||||
// First word needs to fit in reduced width if there's an indent
|
// First word needs to fit in reduced width if there's an indent
|
||||||
@@ -522,18 +524,13 @@ std::vector<size_t> ParsedText::computeLineBreaks(const GfxRenderer& renderer, c
|
|||||||
size_t ParsedText::computeSingleLineBreakNoHyphen(const GfxRenderer& renderer, const int fontId, const int pageWidth,
|
size_t ParsedText::computeSingleLineBreakNoHyphen(const GfxRenderer& renderer, const int fontId, const int pageWidth,
|
||||||
const std::vector<uint16_t>& wordWidths,
|
const std::vector<uint16_t>& wordWidths,
|
||||||
const std::vector<bool>& continuesVec,
|
const std::vector<bool>& continuesVec,
|
||||||
const size_t lineStartIndex) const {
|
const size_t lineStartIndex, const int firstLineIndent) const {
|
||||||
// One-line non-hyphenating breaker used by the page-boundary retry path.
|
// One-line non-hyphenating breaker used by the page-boundary retry path.
|
||||||
if (lineStartIndex >= wordWidths.size()) {
|
if (lineStartIndex >= wordWidths.size()) {
|
||||||
return lineStartIndex;
|
return lineStartIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
const int firstLineIndent =
|
const int effectivePageWidth = pageWidth - (lineStartIndex == 0 ? firstLineIndent : 0);
|
||||||
lineStartIndex == 0 && blockStyle.textIndentDefined &&
|
|
||||||
(blockStyle.alignment == CssTextAlign::Justify || blockStyle.alignment == CssTextAlign::Left)
|
|
||||||
? std::min(std::max<int>(static_cast<int>(blockStyle.textIndent), -(pageWidth - 1)), pageWidth - 1)
|
|
||||||
: 0;
|
|
||||||
const int effectivePageWidth = pageWidth - firstLineIndent;
|
|
||||||
|
|
||||||
size_t currentIndex = lineStartIndex;
|
size_t currentIndex = lineStartIndex;
|
||||||
int lineWidth = 0;
|
int lineWidth = 0;
|
||||||
@@ -672,15 +669,8 @@ std::vector<size_t> ParsedText::computeHyphenatedLineBreaks(const GfxRenderer& r
|
|||||||
std::vector<bool>& continuesVec,
|
std::vector<bool>& continuesVec,
|
||||||
std::vector<bool>& lineEndsWithHyphenatedWord,
|
std::vector<bool>& lineEndsWithHyphenatedWord,
|
||||||
std::vector<int>& splitPrefixWordIndexes,
|
std::vector<int>& splitPrefixWordIndexes,
|
||||||
std::vector<bool>& splitInsertedHyphen) {
|
std::vector<bool>& splitInsertedHyphen,
|
||||||
// Calculate first line indent (only for left/justified text).
|
const int firstLineIndent) {
|
||||||
// Explicit CSS text-indent always applies — author intent overrides the extraParagraphSpacing
|
|
||||||
// toggle. Only the implicit EmSpace fallback in applyParagraphIndent() is gated on it.
|
|
||||||
const int firstLineIndent =
|
|
||||||
blockStyle.textIndentDefined &&
|
|
||||||
(blockStyle.alignment == CssTextAlign::Justify || blockStyle.alignment == CssTextAlign::Left)
|
|
||||||
? std::min(std::max<int>(static_cast<int>(blockStyle.textIndent), -(pageWidth - 1)), pageWidth - 1)
|
|
||||||
: 0;
|
|
||||||
|
|
||||||
// Pre-compute inter-word gaps to avoid repeated codepoint scanning and renderer
|
// Pre-compute inter-word gaps to avoid repeated codepoint scanning and renderer
|
||||||
// calls in the inner loop. When hyphenateWordAtIndex inserts a new word, we insert
|
// calls in the inner loop. When hyphenateWordAtIndex inserts a new word, we insert
|
||||||
@@ -960,20 +950,14 @@ ParsedText::LineProcessResult ParsedText::extractLine(
|
|||||||
const std::vector<bool>& continuesVec, const std::vector<size_t>& lineBreakIndices,
|
const std::vector<bool>& continuesVec, const std::vector<size_t>& lineBreakIndices,
|
||||||
const std::function<LineProcessResult(std::shared_ptr<TextBlock>, bool, bool)>& processLine,
|
const std::function<LineProcessResult(std::shared_ptr<TextBlock>, bool, bool)>& processLine,
|
||||||
const GfxRenderer& renderer, const int fontId, const bool lineEndsWithHyphenatedWord,
|
const GfxRenderer& renderer, const int fontId, const bool lineEndsWithHyphenatedWord,
|
||||||
const bool suppressHyphenationRetry) {
|
const bool suppressHyphenationRetry, const int firstLineIndent) {
|
||||||
const size_t lineBreak = lineBreakIndices[breakIndex];
|
const size_t lineBreak = lineBreakIndices[breakIndex];
|
||||||
const size_t lastBreakAt = breakIndex > 0 ? lineBreakIndices[breakIndex - 1] : 0;
|
const size_t lastBreakAt = breakIndex > 0 ? lineBreakIndices[breakIndex - 1] : 0;
|
||||||
const size_t lineWordCount = lineBreak - lastBreakAt;
|
const size_t lineWordCount = lineBreak - lastBreakAt;
|
||||||
|
|
||||||
// Calculate first line indent (only for left/justified text).
|
// Apply indent only to line 0 of the layout pass; firstLineIndent is already
|
||||||
// Explicit CSS text-indent always applies — author intent overrides the extraParagraphSpacing
|
// 0 for continuation flushes (computed once in layoutAndExtractLines).
|
||||||
// toggle. Only the implicit EmSpace fallback in applyParagraphIndent() is gated on it.
|
const int lineIndent = (breakIndex == 0) ? firstLineIndent : 0;
|
||||||
const bool isFirstLine = breakIndex == 0;
|
|
||||||
const int firstLineIndent =
|
|
||||||
isFirstLine && blockStyle.textIndentDefined &&
|
|
||||||
(blockStyle.alignment == CssTextAlign::Justify || blockStyle.alignment == CssTextAlign::Left)
|
|
||||||
? std::min(std::max<int>(static_cast<int>(blockStyle.textIndent), -(pageWidth - 1)), pageWidth - 1)
|
|
||||||
: 0;
|
|
||||||
|
|
||||||
// Calculate total word width for this line, count actual word gaps,
|
// Calculate total word width for this line, count actual word gaps,
|
||||||
// and accumulate total natural gap widths (including space kerning adjustments).
|
// and accumulate total natural gap widths (including space kerning adjustments).
|
||||||
@@ -1000,7 +984,7 @@ ParsedText::LineProcessResult ParsedText::extractLine(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Calculate spacing (account for indent reducing effective page width on first line)
|
// Calculate spacing (account for indent reducing effective page width on first line)
|
||||||
const int effectivePageWidth = pageWidth - firstLineIndent;
|
const int effectivePageWidth = pageWidth - lineIndent;
|
||||||
// A line is only truly last when it consumes all paragraph words.
|
// A line is only truly last when it consumes all paragraph words.
|
||||||
// During single-line retry we may temporarily pass a truncated break vector,
|
// During single-line retry we may temporarily pass a truncated break vector,
|
||||||
// so relying only on breakIndex would incorrectly disable justification.
|
// so relying only on breakIndex would incorrectly disable justification.
|
||||||
@@ -1014,7 +998,7 @@ ParsedText::LineProcessResult ParsedText::extractLine(
|
|||||||
|
|
||||||
// Calculate initial x position (first line starts at indent for left/justified text;
|
// Calculate initial x position (first line starts at indent for left/justified text;
|
||||||
// may be negative for hanging indents, e.g. margin-left:3em; text-indent:-1em).
|
// may be negative for hanging indents, e.g. margin-left:3em; text-indent:-1em).
|
||||||
auto xpos = static_cast<int16_t>(firstLineIndent);
|
auto xpos = static_cast<int16_t>(lineIndent);
|
||||||
if (blockStyle.alignment == CssTextAlign::Right) {
|
if (blockStyle.alignment == CssTextAlign::Right) {
|
||||||
xpos = effectivePageWidth - lineWordWidthSum - totalNaturalGaps;
|
xpos = effectivePageWidth - lineWordWidthSum - totalNaturalGaps;
|
||||||
} else if (blockStyle.alignment == CssTextAlign::Center) {
|
} else if (blockStyle.alignment == CssTextAlign::Center) {
|
||||||
|
|||||||
@@ -32,12 +32,14 @@ class ParsedText {
|
|||||||
void applyParagraphIndent();
|
void applyParagraphIndent();
|
||||||
void applyBionicReadingTransform();
|
void applyBionicReadingTransform();
|
||||||
std::vector<size_t> computeLineBreaks(const GfxRenderer& renderer, int fontId, int pageWidth,
|
std::vector<size_t> computeLineBreaks(const GfxRenderer& renderer, int fontId, int pageWidth,
|
||||||
std::vector<uint16_t>& wordWidths, std::vector<bool>& continuesVec);
|
std::vector<uint16_t>& wordWidths, std::vector<bool>& continuesVec,
|
||||||
|
int firstLineIndent);
|
||||||
std::vector<size_t> computeHyphenatedLineBreaks(const GfxRenderer& renderer, int fontId, int pageWidth,
|
std::vector<size_t> computeHyphenatedLineBreaks(const GfxRenderer& renderer, int fontId, int pageWidth,
|
||||||
std::vector<uint16_t>& wordWidths, std::vector<bool>& continuesVec,
|
std::vector<uint16_t>& wordWidths, std::vector<bool>& continuesVec,
|
||||||
std::vector<bool>& lineEndsWithHyphenatedWord,
|
std::vector<bool>& lineEndsWithHyphenatedWord,
|
||||||
std::vector<int>& splitPrefixWordIndexes,
|
std::vector<int>& splitPrefixWordIndexes,
|
||||||
std::vector<bool>& splitInsertedHyphen);
|
std::vector<bool>& splitInsertedHyphen,
|
||||||
|
int firstLineIndent);
|
||||||
// Recompute hyphenated breaks for a suffix that starts at startIndex.
|
// Recompute hyphenated breaks for a suffix that starts at startIndex.
|
||||||
// Used after a single-line retry so later lines keep normal hyphenation.
|
// Used after a single-line retry so later lines keep normal hyphenation.
|
||||||
std::vector<size_t> computeHyphenatedLineBreaksFromIndex(const GfxRenderer& renderer, int fontId, int pageWidth,
|
std::vector<size_t> computeHyphenatedLineBreaksFromIndex(const GfxRenderer& renderer, int fontId, int pageWidth,
|
||||||
@@ -50,7 +52,7 @@ class ParsedText {
|
|||||||
// Used only for the page-boundary retry line.
|
// Used only for the page-boundary retry line.
|
||||||
size_t computeSingleLineBreakNoHyphen(const GfxRenderer& renderer, int fontId, int pageWidth,
|
size_t computeSingleLineBreakNoHyphen(const GfxRenderer& renderer, int fontId, int pageWidth,
|
||||||
const std::vector<uint16_t>& wordWidths, const std::vector<bool>& continuesVec,
|
const std::vector<uint16_t>& wordWidths, const std::vector<bool>& continuesVec,
|
||||||
size_t lineStartIndex) const;
|
size_t lineStartIndex, int firstLineIndent) const;
|
||||||
bool hyphenateWordAtIndex(size_t wordIndex, int availableWidth, const GfxRenderer& renderer, int fontId,
|
bool hyphenateWordAtIndex(size_t wordIndex, int availableWidth, const GfxRenderer& renderer, int fontId,
|
||||||
std::vector<uint16_t>& wordWidths, bool allowFallbackBreaks,
|
std::vector<uint16_t>& wordWidths, bool allowFallbackBreaks,
|
||||||
bool* outInsertedHyphen = nullptr);
|
bool* outInsertedHyphen = nullptr);
|
||||||
@@ -58,7 +60,8 @@ class ParsedText {
|
|||||||
size_t breakIndex, int pageWidth, const std::vector<uint16_t>& wordWidths, const std::vector<bool>& continuesVec,
|
size_t breakIndex, int pageWidth, const std::vector<uint16_t>& wordWidths, const std::vector<bool>& continuesVec,
|
||||||
const std::vector<size_t>& lineBreakIndices,
|
const std::vector<size_t>& lineBreakIndices,
|
||||||
const std::function<LineProcessResult(std::shared_ptr<TextBlock>, bool, bool)>& processLine,
|
const std::function<LineProcessResult(std::shared_ptr<TextBlock>, bool, bool)>& processLine,
|
||||||
const GfxRenderer& renderer, int fontId, bool lineEndsWithHyphenatedWord, bool suppressHyphenationRetry);
|
const GfxRenderer& renderer, int fontId, bool lineEndsWithHyphenatedWord, bool suppressHyphenationRetry,
|
||||||
|
int firstLineIndent);
|
||||||
std::vector<uint16_t> calculateWordWidths(const GfxRenderer& renderer, int fontId);
|
std::vector<uint16_t> calculateWordWidths(const GfxRenderer& renderer, int fontId);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|||||||
Reference in New Issue
Block a user