diff --git a/lib/Epub/Epub/blocks/BlockStyle.h b/lib/Epub/Epub/blocks/BlockStyle.h index 63b054c9..a5a616bf 100644 --- a/lib/Epub/Epub/blocks/BlockStyle.h +++ b/lib/Epub/Epub/blocks/BlockStyle.h @@ -64,21 +64,27 @@ struct BlockStyle { // Create a BlockStyle from CSS style properties, resolving CssLength values to pixels // emSize is the current font line height, used for em/rem unit conversion // paragraphAlignment is the user's paragraphAlignment setting preference - static BlockStyle fromCssStyle(const CssStyle& cssStyle, const float emSize, const CssTextAlign paragraphAlignment) { + static BlockStyle fromCssStyle(const CssStyle& cssStyle, const float emSize, const CssTextAlign paragraphAlignment, + const uint16_t viewportWidth = 0) { BlockStyle blockStyle; - // Resolve all CssLength values to pixels using the current font's em size - blockStyle.marginTop = cssStyle.marginTop.toPixelsInt16(emSize); - blockStyle.marginBottom = cssStyle.marginBottom.toPixelsInt16(emSize); - blockStyle.marginLeft = cssStyle.marginLeft.toPixelsInt16(emSize); - blockStyle.marginRight = cssStyle.marginRight.toPixelsInt16(emSize); + const float vw = viewportWidth; + // Resolve all CssLength values to pixels using the current font's em size and viewport width + blockStyle.marginTop = cssStyle.marginTop.toPixelsInt16(emSize, vw); + blockStyle.marginBottom = cssStyle.marginBottom.toPixelsInt16(emSize, vw); + blockStyle.marginLeft = cssStyle.marginLeft.toPixelsInt16(emSize, vw); + blockStyle.marginRight = cssStyle.marginRight.toPixelsInt16(emSize, vw); - blockStyle.paddingTop = cssStyle.paddingTop.toPixelsInt16(emSize); - blockStyle.paddingBottom = cssStyle.paddingBottom.toPixelsInt16(emSize); - blockStyle.paddingLeft = cssStyle.paddingLeft.toPixelsInt16(emSize); - blockStyle.paddingRight = cssStyle.paddingRight.toPixelsInt16(emSize); + blockStyle.paddingTop = cssStyle.paddingTop.toPixelsInt16(emSize, vw); + blockStyle.paddingBottom = cssStyle.paddingBottom.toPixelsInt16(emSize, vw); + blockStyle.paddingLeft = cssStyle.paddingLeft.toPixelsInt16(emSize, vw); + blockStyle.paddingRight = cssStyle.paddingRight.toPixelsInt16(emSize, vw); - blockStyle.textIndent = cssStyle.textIndent.toPixelsInt16(emSize); - blockStyle.textIndentDefined = cssStyle.hasTextIndent(); + // For textIndent: if it's a percentage we can't resolve (no viewport width), + // leave textIndentDefined=false so the EmSpace fallback in applyParagraphIndent() is used + if (cssStyle.hasTextIndent() && cssStyle.textIndent.isResolvable(vw)) { + blockStyle.textIndent = cssStyle.textIndent.toPixelsInt16(emSize, vw); + blockStyle.textIndentDefined = true; + } blockStyle.textAlignDefined = cssStyle.hasTextAlign(); // User setting overrides CSS, unless "Book's Style" alignment setting is selected if (paragraphAlignment == CssTextAlign::None) { diff --git a/lib/Epub/Epub/css/CssParser.cpp b/lib/Epub/Epub/css/CssParser.cpp index d51ebba7..ba187e4c 100644 --- a/lib/Epub/Epub/css/CssParser.cpp +++ b/lib/Epub/Epub/css/CssParser.cpp @@ -283,6 +283,8 @@ CssLength CssParser::interpretLength(const std::string& val) { unit = CssUnit::Rem; } else if (unitPart == "pt") { unit = CssUnit::Points; + } else if (unitPart == "%") { + unit = CssUnit::Percent; } // px and unitless default to Pixels @@ -518,7 +520,7 @@ CssStyle CssParser::parseInlineStyle(const std::string& styleValue) { return par // Cache serialization // Cache format version - increment when format changes -constexpr uint8_t CSS_CACHE_VERSION = 1; +constexpr uint8_t CSS_CACHE_VERSION = 2; bool CssParser::saveToCache(FsFile& file) const { if (!file) { diff --git a/lib/Epub/Epub/css/CssStyle.h b/lib/Epub/Epub/css/CssStyle.h index adbc19e2..b90fa7ab 100644 --- a/lib/Epub/Epub/css/CssStyle.h +++ b/lib/Epub/Epub/css/CssStyle.h @@ -4,7 +4,7 @@ // Matches order of PARAGRAPH_ALIGNMENT in CrossPointSettings enum class CssTextAlign : uint8_t { Justify = 0, Left = 1, Center = 2, Right = 3, None = 4 }; -enum class CssUnit : uint8_t { Pixels = 0, Em = 1, Rem = 2, Points = 3 }; +enum class CssUnit : uint8_t { Pixels = 0, Em = 1, Rem = 2, Points = 3, Percent = 4 }; // Represents a CSS length value with its unit, allowing deferred resolution to pixels struct CssLength { @@ -17,21 +17,32 @@ struct CssLength { // Convenience constructor for pixel values (most common case) explicit CssLength(const float pixels) : value(pixels) {} + // Returns true if this length can be resolved to pixels with the given context. + // Percentage units require a non-zero containerWidth to resolve. + [[nodiscard]] bool isResolvable(const float containerWidth = 0) const { + return unit != CssUnit::Percent || containerWidth > 0; + } + // Resolve to pixels given the current em size (font line height) - [[nodiscard]] float toPixels(const float emSize) const { + // containerWidth is needed for percentage units (e.g. viewport width) + [[nodiscard]] float toPixels(const float emSize, const float containerWidth = 0) const { switch (unit) { case CssUnit::Em: case CssUnit::Rem: return value * emSize; case CssUnit::Points: return value * 1.33f; // Approximate pt to px conversion + case CssUnit::Percent: + return value * containerWidth / 100.0f; default: return value; } } // Resolve to int16_t pixels (for BlockStyle fields) - [[nodiscard]] int16_t toPixelsInt16(const float emSize) const { return static_cast(toPixels(emSize)); } + [[nodiscard]] int16_t toPixelsInt16(const float emSize, const float containerWidth = 0) const { + return static_cast(toPixels(emSize, containerWidth)); + } }; // Font style options matching CSS font-style property diff --git a/lib/Epub/Epub/parsers/ChapterHtmlSlimParser.cpp b/lib/Epub/Epub/parsers/ChapterHtmlSlimParser.cpp index 043cfca7..a4d10832 100644 --- a/lib/Epub/Epub/parsers/ChapterHtmlSlimParser.cpp +++ b/lib/Epub/Epub/parsers/ChapterHtmlSlimParser.cpp @@ -213,12 +213,12 @@ void XMLCALL ChapterHtmlSlimParser::startElement(void* userData, const XML_Char* } const float emSize = static_cast(self->renderer.getLineHeight(self->fontId)) * self->lineCompression; - const auto userAlignmentBlockStyle = - BlockStyle::fromCssStyle(cssStyle, emSize, static_cast(self->paragraphAlignment)); + const auto userAlignmentBlockStyle = BlockStyle::fromCssStyle( + cssStyle, emSize, static_cast(self->paragraphAlignment), self->viewportWidth); if (matches(name, HEADER_TAGS, NUM_HEADER_TAGS)) { self->currentCssStyle = cssStyle; - auto headerBlockStyle = BlockStyle::fromCssStyle(cssStyle, emSize, CssTextAlign::Center); + auto headerBlockStyle = BlockStyle::fromCssStyle(cssStyle, emSize, CssTextAlign::Center, self->viewportWidth); headerBlockStyle.textAlignDefined = true; if (self->embeddedStyle && cssStyle.hasTextAlign()) { headerBlockStyle.alignment = cssStyle.textAlign;