From ba07a6666d01688d2fb6f73eb7d0f4e3963e45cb Mon Sep 17 00:00:00 2001 From: jpirnay Date: Mon, 30 Mar 2026 13:38:46 +0200 Subject: [PATCH] Fix br alignment reset behaviour --- lib/Epub/Epub/blocks/BlockStyle.h | 7 ++++ .../Epub/parsers/ChapterHtmlSlimParser.cpp | 41 +++++++++++++++---- 2 files changed, 41 insertions(+), 7 deletions(-) diff --git a/lib/Epub/Epub/blocks/BlockStyle.h b/lib/Epub/Epub/blocks/BlockStyle.h index a5a616bf..db56f6b7 100644 --- a/lib/Epub/Epub/blocks/BlockStyle.h +++ b/lib/Epub/Epub/blocks/BlockStyle.h @@ -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
element. Used by startNewTextBlock to inject + // a full line-height gap when the
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
block is merged with the following paragraph. + combinedBlockStyle.fromBrElement = false; return combinedBlockStyle; } diff --git a/lib/Epub/Epub/parsers/ChapterHtmlSlimParser.cpp b/lib/Epub/Epub/parsers/ChapterHtmlSlimParser.cpp index 6865e014..12c12c30 100644 --- a/lib/Epub/Epub/parsers/ChapterHtmlSlimParser.cpp +++ b/lib/Epub/Epub/parsers/ChapterHtmlSlimParser.cpp @@ -133,7 +133,21 @@ void ChapterHtmlSlimParser::startNewTextBlock(const BlockStyle& blockStyle) { // Merge with existing block style to accumulate CSS styling from parent block elements. // This handles cases like

text

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; + const bool brGapPending = currentTextBlock->getBlockStyle().fromBrElement; + if (brGapPending) { + // The empty block was created by a
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
block stayed empty (i.e. no inline text was added). + const int16_t lineHeight = static_cast(renderer.getLineHeight(fontId) * lineCompression + 0.5f); + incoming.marginTop = static_cast(incoming.marginTop + lineHeight); + } + + BlockStyle merged = currentTextBlock->getBlockStyle().getCombinedBlockStyle(incoming); + // Preserve only whether the current empty block still represents
separators. + // This lets consecutive
accumulate one line each without leaking the flag to real content blocks. + merged.fromBrElement = blockStyle.fromBrElement; + currentTextBlock->setBlockStyle(merged); if (!pendingAnchorId.empty()) { anchorData.push_back({std::move(pendingAnchorId), static_cast(completedPageCount)}); @@ -574,7 +588,16 @@ void XMLCALL ChapterHtmlSlimParser::startElement(void* userData, const XML_Char* // flush word preceding
to currentTextBlock before calling startNewTextBlock self->flushPartWordBuffer(); } - self->startNewTextBlock(self->currentTextBlock->getBlockStyle()); + // Build a neutral
style that keeps inline alignment/indent context but avoids + // carrying cumulative margins from previous empty blocks (which can force spurious page breaks). + const BlockStyle& currentStyle = self->currentTextBlock->getBlockStyle(); + BlockStyle brStyle; + brStyle.alignment = currentStyle.alignment; + brStyle.textAlignDefined = currentStyle.textAlignDefined; + brStyle.textIndent = currentStyle.textIndent; + brStyle.textIndentDefined = currentStyle.textIndentDefined; + brStyle.fromBrElement = true; + self->startNewTextBlock(brStyle); } else { self->currentCssStyle = cssStyle; self->startNewTextBlock(userAlignmentBlockStyle); @@ -986,11 +1009,15 @@ void XMLCALL ChapterHtmlSlimParser::endElement(void* userData, const XML_Char* n // Margins/padding are preserved so parent element spacing still accumulates correctly. if (self->currentTextBlock && self->currentTextBlock->isEmpty()) { auto style = self->currentTextBlock->getBlockStyle(); - style.textAlignDefined = false; - style.alignment = (self->paragraphAlignment == static_cast(CssTextAlign::None)) - ? CssTextAlign::Justify - : static_cast(self->paragraphAlignment); - self->currentTextBlock->setBlockStyle(style); + // Keep alignment for synthetic empty
separator blocks so following inline + // text after
inside centered/right-aligned containers preserves alignment. + if (!style.fromBrElement) { + style.textAlignDefined = false; + style.alignment = (self->paragraphAlignment == static_cast(CssTextAlign::None)) + ? CssTextAlign::Justify + : static_cast(self->paragraphAlignment); + self->currentTextBlock->setBlockStyle(style); + } } } }