This commit is contained in:
jpirnay
2026-03-26 11:05:13 +01:00
parent ac42d5ef36
commit 1af3f81336
4 changed files with 235 additions and 2 deletions
+7
View File
@@ -22,6 +22,10 @@ struct BlockStyle {
int16_t textIndent = 0;
bool textIndentDefined = false; // true if text-indent was explicitly set in CSS
bool textAlignDefined = false; // true if text-align was explicitly set in CSS
// Set when this block was created by a <br> element. Used by startNewTextBlock to inject
// a full line-height gap when the <br> block stays empty (section-break use case).
// NOT propagated through getCombinedBlockStyle so it can't leak into sibling blocks.
bool fromBrElement = false;
// Combined horizontal insets (margin + padding)
[[nodiscard]] int16_t leftInset() const { return marginLeft + paddingLeft; }
@@ -58,6 +62,9 @@ struct BlockStyle {
combinedBlockStyle.alignment = alignment;
combinedBlockStyle.textAlignDefined = textAlignDefined;
}
// fromBrElement is never propagated — it is consumed by startNewTextBlock
// when the empty <br> block is merged with the following paragraph.
combinedBlockStyle.fromBrElement = false;
return combinedBlockStyle;
}
@@ -133,7 +133,17 @@ void ChapterHtmlSlimParser::startNewTextBlock(const BlockStyle& blockStyle) {
// Merge with existing block style to accumulate CSS styling from parent block elements.
// This handles cases like <div style="margin-bottom:2em"><h1>text</h1></div> where the
// div's margin should be preserved, even though it has no direct text content.
currentTextBlock->setBlockStyle(currentTextBlock->getBlockStyle().getCombinedBlockStyle(blockStyle));
BlockStyle incoming = blockStyle;
if (currentTextBlock->getBlockStyle().fromBrElement) {
// The empty block was created by a <br> section separator. Inject a full line of
// blank space before the following paragraph so the scene/section break is visible.
// This only fires when the <br> block stayed empty (i.e. no inline text was added).
const int16_t lineHeight = static_cast<int16_t>(renderer.getLineHeight(fontId) * lineCompression + 0.5f);
incoming.marginTop = static_cast<int16_t>(incoming.marginTop + lineHeight);
}
currentTextBlock->setBlockStyle(currentTextBlock->getBlockStyle().getCombinedBlockStyle(incoming));
if (!pendingAnchorId.empty()) {
anchorData.push_back({std::move(pendingAnchorId), static_cast<uint16_t>(completedPageCount)});
@@ -587,7 +597,13 @@ void XMLCALL ChapterHtmlSlimParser::startElement(void* userData, const XML_Char*
// flush word preceding <br/> to currentTextBlock before calling startNewTextBlock
self->flushPartWordBuffer();
}
self->startNewTextBlock(self->currentTextBlock->getBlockStyle());
// Tag the new block so startNewTextBlock can inject a full line-height gap if
// the block remains empty (i.e. <br> is a section separator between paragraphs).
// If the block gets text added before the next block opens it becomes non-empty,
// goes through makePages() normally, and the flag has no effect (inline <br> case).
BlockStyle brStyle = self->currentTextBlock->getBlockStyle();
brStyle.fromBrElement = true;
self->startNewTextBlock(brStyle);
} else {
self->currentCssStyle = cssStyle;
self->startNewTextBlock(userAlignmentBlockStyle);