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);
+ }
}
}
}