...
+// spacers inside running prose. Keep them as paragraph boundaries, but ignore +// their inner text payload (usually NBSP) to avoid no-break-space glue artifacts. +bool isZeroHeightSpacerParagraph(const char* name, const std::string& styleAttr) { + if (strcmp(name, "p") != 0 || styleAttr.empty()) { + return false; + } + + std::string normalized; + normalized.reserve(styleAttr.size()); + for (const char ch : styleAttr) { + if (!isWhitespace(ch)) { + normalized.push_back(static_cast, but skip its inner text payload.
+ self->currentCssStyle = cssStyle;
+ auto blockStyle = userAlignmentBlockStyle;
+ if (self->embeddedStyle && cssStyle.hasTextAlign()) {
+ blockStyle.alignment = cssStyle.textAlign;
+ blockStyle.textAlignDefined = true;
+ }
+ self->startNewTextBlock(blockStyle);
+ self->updateEffectiveInlineStyle();
+
+ self->skipTextUntilDepth = self->depth;
+ self->depth += 1;
+ return;
+ }
+
if (strcmp(name, "br") == 0) {
if (self->partWordBufferIndex > 0) {
// flush word preceding
to currentTextBlock before calling startNewTextBlock
@@ -800,6 +839,11 @@ void XMLCALL ChapterHtmlSlimParser::characterData(void* userData, const XML_Char
return;
}
+ // Ignore character data inside synthetic zero-height spacer
tags.
+ if (self->skipTextUntilDepth < self->depth) {
+ return;
+ }
+
// Collect footnote link display text (for the number label)
// Skip whitespace and brackets to normalize noterefs like "[1]" → "1"
if (self->insideFootnoteLink) {
@@ -1050,6 +1094,11 @@ void XMLCALL ChapterHtmlSlimParser::endElement(void* userData, const XML_Char* n
self->skipUntilDepth = INT_MAX;
}
+ // Leaving zero-height spacer paragraph text-skip scope
+ if (self->skipTextUntilDepth == self->depth) {
+ self->skipTextUntilDepth = INT_MAX;
+ }
+
if (self->tableDepth == 1 && (strcmp(name, "td") == 0 || strcmp(name, "th") == 0)) {
self->nextWordContinues = false;
}
@@ -1104,9 +1153,11 @@ 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();
- // Keep alignment for synthetic empty
int boldUntilDepth = INT_MAX;
int italicUntilDepth = INT_MAX;
int underlineUntilDepth = INT_MAX;
separator blocks so following inline
- // text after
inside centered/right-aligned containers preserves alignment.
- if (!style.fromBrElement) {
+ // Keep alignment only when closing the
separator itself so subsequent text
+ // within the same block container stays aligned. Reset alignment when closing
+ // other block tags (e.g. div/p) to avoid leaking centered/right alignment globally.
+ const bool preserveForBrClose = style.fromBrElement && strcmp(name, "br") == 0;
+ if (!preserveForBrClose) {
style.textAlignDefined = false;
style.alignment = (self->paragraphAlignment == static_cast