diff --git a/lib/Epub/Epub/parsers/ChapterHtmlSlimParser.cpp b/lib/Epub/Epub/parsers/ChapterHtmlSlimParser.cpp index 1c4bd1b4e..4d71614b9 100644 --- a/lib/Epub/Epub/parsers/ChapterHtmlSlimParser.cpp +++ b/lib/Epub/Epub/parsers/ChapterHtmlSlimParser.cpp @@ -338,18 +338,29 @@ void XMLCALL ChapterHtmlSlimParser::startElement(void* userData, const XML_Char* const bool hasCssHeight = imgStyle.hasImageHeight(); const bool hasCssWidth = imgStyle.hasImageWidth(); + // Compute effective container width for percentage-based image sizes. + // If the image is inside a block with horizontal margins/padding (e.g. + //
), percentage widths like width:100% + // should resolve against the container width, not the full viewport. + int containerWidth = self->viewportWidth; + if (self->currentTextBlock) { + const int inset = self->currentTextBlock->getBlockStyle().totalHorizontalInset(); + if (inset > 0 && inset < self->viewportWidth) { + containerWidth = self->viewportWidth - inset; + } + } + if (hasCssHeight && hasCssWidth && dims.width > 0 && dims.height > 0) { // Both CSS height and width set: resolve both, then clamp to viewport preserving requested ratio displayHeight = static_cast( imgStyle.imageHeight.toPixels(emSize, static_cast(self->viewportHeight)) + 0.5f); - displayWidth = static_cast( - imgStyle.imageWidth.toPixels(emSize, static_cast(self->viewportWidth)) + 0.5f); + displayWidth = + static_cast(imgStyle.imageWidth.toPixels(emSize, static_cast(containerWidth)) + 0.5f); if (displayHeight < 1) displayHeight = 1; if (displayWidth < 1) displayWidth = 1; - if (displayWidth > self->viewportWidth || displayHeight > self->viewportHeight) { - float scaleX = (displayWidth > self->viewportWidth) - ? static_cast(self->viewportWidth) / displayWidth - : 1.0f; + if (displayWidth > containerWidth || displayHeight > self->viewportHeight) { + float scaleX = + (displayWidth > containerWidth) ? static_cast(containerWidth) / displayWidth : 1.0f; float scaleY = (displayHeight > self->viewportHeight) ? static_cast(self->viewportHeight) / displayHeight : 1.0f; @@ -374,8 +385,8 @@ void XMLCALL ChapterHtmlSlimParser::startElement(void* userData, const XML_Char* static_cast(displayHeight * (static_cast(dims.width) / dims.height) + 0.5f); if (displayWidth < 1) displayWidth = 1; } - if (displayWidth > self->viewportWidth) { - displayWidth = self->viewportWidth; + if (displayWidth > containerWidth) { + displayWidth = containerWidth; // Rescale height to preserve aspect ratio when width is clamped displayHeight = static_cast(displayWidth * (static_cast(dims.height) / dims.width) + 0.5f); @@ -384,10 +395,10 @@ void XMLCALL ChapterHtmlSlimParser::startElement(void* userData, const XML_Char* if (displayWidth < 1) displayWidth = 1; LOG_DBG("EHP", "Display size from CSS height: %dx%d", displayWidth, displayHeight); } else if (hasCssWidth && !hasCssHeight && dims.width > 0 && dims.height > 0) { - // Use CSS width (resolve % against viewport width) and derive height from aspect ratio - displayWidth = static_cast( - imgStyle.imageWidth.toPixels(emSize, static_cast(self->viewportWidth)) + 0.5f); - if (displayWidth > self->viewportWidth) displayWidth = self->viewportWidth; + // Use CSS width (resolve % against container width) and derive height from aspect ratio + displayWidth = + static_cast(imgStyle.imageWidth.toPixels(emSize, static_cast(containerWidth)) + 0.5f); + if (displayWidth > containerWidth) displayWidth = containerWidth; if (displayWidth < 1) displayWidth = 1; displayHeight = static_cast(displayWidth * (static_cast(dims.height) / dims.width) + 0.5f); @@ -401,8 +412,8 @@ void XMLCALL ChapterHtmlSlimParser::startElement(void* userData, const XML_Char* if (displayHeight < 1) displayHeight = 1; LOG_DBG("EHP", "Display size from CSS width: %dx%d", displayWidth, displayHeight); } else { - // Scale to fit viewport while maintaining aspect ratio - int maxWidth = self->viewportWidth; + // Scale to fit container while maintaining aspect ratio + int maxWidth = containerWidth; int maxHeight = self->viewportHeight; float scaleX = (dims.width > maxWidth) ? (float)maxWidth / dims.width : 1.0f; float scaleY = (dims.height > maxHeight) ? (float)maxHeight / dims.height : 1.0f; @@ -423,9 +434,21 @@ void XMLCALL ChapterHtmlSlimParser::startElement(void* userData, const XML_Char* self->startNewTextBlock(parentBlockStyle); } + // Apply vertical margins from the current (possibly empty) text block. + // When an image is inside a container like
, + // the div's margins live on the empty text block but are never flushed + // via makePages(). Apply them here so the image respects vertical spacing. + int16_t imageMarginTop = 0; + int16_t imageMarginBottom = 0; + if (self->currentTextBlock && self->currentTextBlock->isEmpty()) { + const auto& bs = self->currentTextBlock->getBlockStyle(); + imageMarginTop = static_cast(bs.marginTop + bs.paddingTop); + imageMarginBottom = static_cast(bs.marginBottom + bs.paddingBottom); + } + // Create page for image - only break if image won't fit remaining space if (self->currentPage && !self->currentPage->elements.empty() && - (self->currentPageNextY + displayHeight > self->viewportHeight)) { + (self->currentPageNextY + imageMarginTop + displayHeight > self->viewportHeight)) { self->completePageFn(std::move(self->currentPage)); self->completedPageCount++; self->currentPage.reset(new Page()); @@ -443,6 +466,9 @@ void XMLCALL ChapterHtmlSlimParser::startElement(void* userData, const XML_Char* self->currentPageNextY = 0; } + // Apply top margin from container block + self->currentPageNextY += imageMarginTop; + // Create ImageBlock and add to page auto imageBlock = std::make_shared(cachedImagePath, displayWidth, displayHeight); if (!imageBlock) { @@ -456,7 +482,7 @@ void XMLCALL ChapterHtmlSlimParser::startElement(void* userData, const XML_Char* return; } self->currentPage->elements.push_back(pageImage); - self->currentPageNextY += displayHeight; + self->currentPageNextY += displayHeight + imageMarginBottom; self->depth += 1; return;