diff --git a/bin/unit_tests/assets/html/anchor_padding.html b/bin/unit_tests/assets/html/anchor_padding.html new file mode 100644 index 000000000..c9d3646cd --- /dev/null +++ b/bin/unit_tests/assets/html/anchor_padding.html @@ -0,0 +1,65 @@ + + + + + +
+
+
+

Windows

+ Download +
+
+
+ + diff --git a/bin/unit_tests/assets/html/anchor_padding_lineheight.html b/bin/unit_tests/assets/html/anchor_padding_lineheight.html new file mode 100644 index 000000000..be0bb34b8 --- /dev/null +++ b/bin/unit_tests/assets/html/anchor_padding_lineheight.html @@ -0,0 +1,65 @@ + + + + + +
+
+
+

Windows

+ Download +
+
+
+ + diff --git a/bin/unit_tests/assets/html/eepp-ui-anchor-padding-lineheight.webp b/bin/unit_tests/assets/html/eepp-ui-anchor-padding-lineheight.webp new file mode 100644 index 000000000..5cdf1db37 Binary files /dev/null and b/bin/unit_tests/assets/html/eepp-ui-anchor-padding-lineheight.webp differ diff --git a/bin/unit_tests/assets/html/eepp-ui-anchor-padding.webp b/bin/unit_tests/assets/html/eepp-ui-anchor-padding.webp new file mode 100644 index 000000000..bbbb9fc64 Binary files /dev/null and b/bin/unit_tests/assets/html/eepp-ui-anchor-padding.webp differ diff --git a/bin/unit_tests/assets/html/eepp-ui-inline-block-image-spans.webp b/bin/unit_tests/assets/html/eepp-ui-inline-block-image-spans.webp new file mode 100644 index 000000000..c44d05b20 Binary files /dev/null and b/bin/unit_tests/assets/html/eepp-ui-inline-block-image-spans.webp differ diff --git a/bin/unit_tests/assets/html/inline_block.html b/bin/unit_tests/assets/html/inline_block.html new file mode 100644 index 000000000..b0b5edf86 --- /dev/null +++ b/bin/unit_tests/assets/html/inline_block.html @@ -0,0 +1,137 @@ + + + + + +
+
+ +
+
+ + diff --git a/include/eepp/graphics/richtext.hpp b/include/eepp/graphics/richtext.hpp index 84a08c182..6e340e504 100644 --- a/include/eepp/graphics/richtext.hpp +++ b/include/eepp/graphics/richtext.hpp @@ -35,7 +35,7 @@ class EE_API RichText : public Drawable { void addSpan( const String& text, const FontStyleConfig& style ); void addSpan( const String& text, const FontStyleConfig& style, const Rectf& margin, - const Rectf& padding ); + const Rectf& padding, Float lineHeight = 0, bool isAtomic = false ); /** * @brief Adds a text span with individual style parameters. @@ -92,6 +92,8 @@ class EE_API RichText : public Drawable { std::shared_ptr text; Rectf margin; Rectf padding; + Float lineHeight{ 0 }; + bool isAtomic{ false }; }; using Block = std::variant, CustomBlock>; diff --git a/include/eepp/ui/uitextspan.hpp b/include/eepp/ui/uitextspan.hpp index f89acabee..9fea7da7d 100644 --- a/include/eepp/ui/uitextspan.hpp +++ b/include/eepp/ui/uitextspan.hpp @@ -43,6 +43,8 @@ class EE_API UITextSpan : public UIRichText { virtual bool isMergeable() const; + virtual bool isInlineBlock() const; + virtual void draw(); virtual bool applyProperty( const StyleSheetProperty& attribute ); diff --git a/src/eepp/graphics/richtext.cpp b/src/eepp/graphics/richtext.cpp index 00a2873a3..f9698d303 100644 --- a/src/eepp/graphics/richtext.cpp +++ b/src/eepp/graphics/richtext.cpp @@ -302,14 +302,14 @@ Sizef RichText::getPixelsSize() { } void RichText::addSpan( const String& text, const FontStyleConfig& style, const Rectf& margin, - const Rectf& padding ) { - if ( text.empty() && margin == Rectf::Zero && padding == Rectf::Zero ) + const Rectf& padding, Float lineHeight, bool isAtomic ) { + if ( text.empty() && margin == Rectf::Zero && padding == Rectf::Zero && lineHeight == 0 ) return; auto span = std::make_shared(); span->setString( text ); span->setStyleConfig( style ); - mBlocks.push_back( SpanBlock{ span, margin, padding } ); + mBlocks.push_back( SpanBlock{ span, margin, padding, lineHeight, isAtomic } ); invalidateLayout(); } @@ -512,6 +512,18 @@ void RichText::updateLayout() { if ( !mLines.empty() ) mLines.back().width += extraLeft; + if ( pText->isAtomic && curX > extraLeft && mMaxWidth > 0 ) { + Float extraRight = pText->margin.Right + pText->padding.Right; + Float fullTextWidth = span->getTextWidth(); + if ( curX + fullTextWidth + extraRight > mMaxWidth ) { + maxWidth = std::max( maxWidth, curX - extraLeft ); + mLines.push_back( RenderParagraph() ); + curX = extraLeft; + if ( !mLines.empty() ) + mLines.back().width += extraLeft; + } + } + Uint32 textHints = span->getTextHints(); // Compute where lines break within this text span. @@ -538,18 +550,20 @@ void RichText::updateLayout() { renderSpanText->setStyleConfig( fontStyle ); Float ascent = fontStyle.Font->getAscent( fontStyle.CharacterSize ); - Float height = mLineHeight > 0 - ? mLineHeight - : fontStyle.Font->getLineSpacing( fontStyle.CharacterSize ); + Float height = + pText->lineHeight > 0 ? pText->lineHeight + : mLineHeight > 0 + ? mLineHeight + : fontStyle.Font->getLineSpacing( fontStyle.CharacterSize ); Float spanWidth = renderSpanText->getTextWidth(); - RenderSpan renderSpan; - renderSpan.block = - SpanBlock{ renderSpanText, pText->margin, pText->padding }; - renderSpan.position = { curX, 0 }; - renderSpan.size = Sizef( spanWidth, height ); - renderSpan.startCharIndex = curCharIdx; - renderSpan.endCharIndex = curCharIdx + ( endIdx - startIdx ); + RenderSpan renderSpan{ + SpanBlock{ renderSpanText, pText->margin, pText->padding, + pText->lineHeight, pText->isAtomic }, + { curX, 0 }, + Sizef( spanWidth, height ), + curCharIdx, + static_cast( curCharIdx + ( endIdx - startIdx ) ) }; curCharIdx = renderSpan.endCharIndex; RenderParagraph& currentLine = mLines.back(); @@ -619,12 +633,7 @@ void RichText::updateLayout() { curX = 0; } - RenderSpan renderSpan; - renderSpan.block = block; - renderSpan.position = { curX, 0 }; - renderSpan.size = blockSize; - renderSpan.startCharIndex = curCharIdx; - renderSpan.endCharIndex = curCharIdx + 1; + RenderSpan renderSpan{ block, { curX, 0 }, blockSize, curCharIdx, curCharIdx + 1 }; curCharIdx = renderSpan.endCharIndex; RenderParagraph& currentLine = mLines.back(); @@ -688,6 +697,8 @@ void RichText::updateLayout() { } line.height = std::max( line.height, maxLineHeight ); + if ( mLineHeight > 0 ) + line.height = std::max( line.height, mLineHeight ); curY += line.height; } @@ -788,6 +799,18 @@ void RichText::updateLayout() { if ( !mLines.empty() ) mLines.back().width += extraLeft; + if ( pText->isAtomic && curX > extraLeft && mMaxWidth > 0 ) { + Float extraRight = pText->margin.Right + pText->padding.Right; + Float fullTextWidth = span->getTextWidth(); + if ( curX + fullTextWidth + extraRight > mMaxWidth ) { + maxWidth = std::max( maxWidth, curX - extraLeft ); + mLines.push_back( RenderParagraph() ); + curX = extraLeft; + if ( !mLines.empty() ) + mLines.back().width += extraLeft; + } + } + // Shift curX inside to the left edge — text starts // to the right of any left floats. Float le = floatLeftEdge( curY ); @@ -821,17 +844,20 @@ void RichText::updateLayout() { renderSpanText->setStyleConfig( fontStyle ); Float ascent = fontStyle.Font->getAscent( fontStyle.CharacterSize ); - Float height = mLineHeight > 0 + Float height = pText->lineHeight > 0 ? pText->lineHeight + : mLineHeight > 0 ? mLineHeight : fontStyle.Font->getLineSpacing( fontStyle.CharacterSize ); Float spanWidth = renderSpanText->getTextWidth(); - RenderSpan renderSpan; - renderSpan.block = SpanBlock{ renderSpanText, pText->margin, pText->padding }; - renderSpan.position = { curX, 0 }; - renderSpan.size = Sizef( spanWidth, height ); - renderSpan.startCharIndex = curCharIdx; - renderSpan.endCharIndex = curCharIdx + ( endIdx - startIdx ); + RenderSpan renderSpan{ + SpanBlock{ renderSpanText, pText->margin, pText->padding, pText->lineHeight, + pText->isAtomic }, + { curX, 0 }, + Sizef( spanWidth, height ), + curCharIdx, + static_cast( curCharIdx + ( endIdx - startIdx ) ) }; + curCharIdx = renderSpan.endCharIndex; RenderParagraph& currentLine = mLines.back(); @@ -844,7 +870,8 @@ void RichText::updateLayout() { currentLine.width += spanWidth; } - // Trailing margin may force a wrap. + // After the last segment, add trailing margin and check if the + // margin itself forces a wrap. if ( i == wrapInfo.wraps.size() - 2 && !isNewline ) { Float extraRight = pText->margin.Right + pText->padding.Right; curX += extraRight; @@ -916,20 +943,14 @@ void RichText::updateLayout() { posX = le; } - RenderSpan renderSpan; - renderSpan.block = block; - renderSpan.position = { posX, 0 }; - renderSpan.size = blockSize; - renderSpan.startCharIndex = curCharIdx; - renderSpan.endCharIndex = curCharIdx + 1; + RenderSpan renderSpan{ block, { posX, 0 }, blockSize, curCharIdx, curCharIdx + 1 }; curCharIdx = renderSpan.endCharIndex; mLines.back().spans.push_back( renderSpan ); // Record the float's bounding box so subsequent // content can wrap around it. - Rectf fr( posX, curY, posX + blockSize.getWidth(), - curY + blockSize.getHeight() ); + Rectf fr( posX, curY, posX + blockSize.getWidth(), curY + blockSize.getHeight() ); if ( floatType == UI::CSSFloat::Left ) leftFloats.push_back( fr ); else @@ -956,12 +977,7 @@ void RichText::updateLayout() { curX = 0; } - RenderSpan renderSpan; - renderSpan.block = block; - renderSpan.position = { curX, 0 }; - renderSpan.size = blockSize; - renderSpan.startCharIndex = curCharIdx; - renderSpan.endCharIndex = curCharIdx + 1; + RenderSpan renderSpan{ block, { curX, 0 }, blockSize, curCharIdx, curCharIdx + 1 }; curCharIdx = renderSpan.endCharIndex; RenderParagraph& currentLine = mLines.back(); @@ -1032,6 +1048,8 @@ void RichText::updateLayout() { } line.height = std::max( line.height, maxLineHeight ); + if ( mLineHeight > 0 ) + line.height = std::max( line.height, mLineHeight ); accumY += line.height; } diff --git a/src/eepp/ui/blocklayouter.cpp b/src/eepp/ui/blocklayouter.cpp index 258f13e07..584e22e82 100644 --- a/src/eepp/ui/blocklayouter.cpp +++ b/src/eepp/ui/blocklayouter.cpp @@ -54,6 +54,10 @@ void BlockLayouter::updateLayout() { auto* rt = widget->getRichTextPtr(); if ( rt == nullptr || mPacking ) return; + + if ( widget->isMergeable() ) + return; + mResizedCount = 0; mPacking = true; @@ -235,11 +239,14 @@ void BlockLayouter::positionRichTextChildren( Graphics::RichText* rt ) { p = p->getParent(); } + bool handled = false; + if ( widget->isType( UI_TYPE_HTML_WIDGET ) && widget->asType()->isMergeable() ) { UITextSpan* textSpan = widget->asType(); Int64 startChar = curCharIdx; Int64 endChar = curCharIdx; + if ( !textSpan->getText().empty() ) { endChar += textSpan->getText().length(); curCharIdx = endChar; @@ -280,6 +287,20 @@ void BlockLayouter::positionRichTextChildren( Graphics::RichText* rt ) { spanChild = spanChild->getNextNode(); } + if ( textSpan->isInlineBlock() ) { + Rectf pad = textSpan->getPixelsPadding(); + bounds.Left -= pad.Left; + bounds.Top -= pad.Top; + bounds.Right += pad.Right; + bounds.Bottom += pad.Bottom; + for ( auto& hb : hitBoxes ) { + hb.Left -= pad.Left; + hb.Top -= pad.Top; + hb.Right += pad.Right; + hb.Bottom += pad.Bottom; + } + } + if ( bounds.Left <= bounds.Right && bounds.Top <= bounds.Bottom ) { Vector2f boundsPos = bounds.getPosition(); @@ -296,34 +317,55 @@ void BlockLayouter::positionRichTextChildren( Graphics::RichText* rt ) { hitBoxes.clear(); } - } else if ( widget->isType( UI_TYPE_BR ) ) { - curCharIdx += 1; - Vector2f pos; - if ( widget->getPrevNode() && widget->getPrevNode()->isWidget() ) { - pos = widget->getPrevNode()->asType()->getPixelsPosition(); - pos.y += widget->getPrevNode()->getPixelsSize().getHeight(); - } - widget->setPixelsPosition( pos ); - widget->setPixelsSize( { eemax( 0.f, mContainer->getPixelsSize().getWidth() - - mContainer->getPixelsContentOffset().Left - - mContainer->getPixelsContentOffset().Right ), - 0 } ); - } else { - curCharIdx += 1; - const auto* span = getNextCustomSpan(); - if ( span ) { - size_t lineIdx = currentSpan > 0 ? currentLine : currentLine - 1; - Float lineY = lines[lineIdx].y; - Rectf margin = widget->getLayoutPixelsMargin(); + handled = true; + } - Vector2f targetPos( mContainer->getPixelsContentOffset().Left + span->position.x + - margin.Left, - mContainer->getPixelsContentOffset().Top + lineY + - span->position.y + margin.Top ); + if ( !handled ) { + if ( widget->isType( UI_TYPE_BR ) ) { + curCharIdx += 1; + Vector2f pos; + if ( widget->getPrevNode() && widget->getPrevNode()->isWidget() ) { + pos = widget->getPrevNode()->asType()->getPixelsPosition(); + pos.y += widget->getPrevNode()->getPixelsSize().getHeight(); + } + widget->setPixelsPosition( pos ); + widget->setPixelsSize( + { eemax( 0.f, mContainer->getPixelsSize().getWidth() - + mContainer->getPixelsContentOffset().Left - + mContainer->getPixelsContentOffset().Right ), + 0 } ); + } else { + curCharIdx += 1; + const auto* span = getNextCustomSpan(); + if ( span ) { + size_t lineIdx = currentSpan > 0 ? currentLine : currentLine - 1; + Float lineY = lines[lineIdx].y; + Rectf margin = widget->getLayoutPixelsMargin(); - widget->setPixelsPosition( targetPos - offset ); + Vector2f targetPos( mContainer->getPixelsContentOffset().Left + + span->position.x + margin.Left, + mContainer->getPixelsContentOffset().Top + lineY + + span->position.y + margin.Top ); - bounds = Rectf( targetPos, span->size ); + widget->setPixelsPosition( targetPos - offset ); + + bounds = Rectf( targetPos, span->size ); + + if ( widget->isType( UI_TYPE_TEXTSPAN ) && + widget->asType()->isInlineBlock() ) { + Rectf pad = widget->getPixelsPadding(); + bounds.Left -= pad.Left; + bounds.Top -= pad.Top; + bounds.Right += pad.Right; + bounds.Bottom += pad.Bottom; + Vector2f boundsPos = bounds.getPosition(); + widget->setPixelsPosition( boundsPos - offset ); + if ( bounds.getSize() != widget->getPixelsSize() ) { + widget->setPixelsSize( bounds.getSize() ); + mResizedCount++; + } + } + } } } return bounds; diff --git a/src/eepp/ui/css/stylesheetspecification.cpp b/src/eepp/ui/css/stylesheetspecification.cpp index 1a5bf0d01..8074f5b1d 100644 --- a/src/eepp/ui/css/stylesheetspecification.cpp +++ b/src/eepp/ui/css/stylesheetspecification.cpp @@ -563,7 +563,8 @@ void StyleSheetSpecification::registerDefaultProperties() { registerShorthand( "list-style", { "list-style-type", "list-style-position", "list-style-image" }, "list-style" ); - registerShorthand( "font", { "font-style", "font-size", "line-spacing", "font-family" }, + registerShorthand( "font", + { "font-style", "font-weight", "font-size", "line-height", "font-family" }, "font" ); } @@ -1356,8 +1357,9 @@ void StyleSheetSpecification::registerDefaultShorthandParsers() { int stylePos = getIndexEndingWith( propNames, "-style" ); int sizePos = getIndexEndingWith( propNames, "-size" ); - int linePos = getIndexEndingWith( propNames, "-spacing" ); + int linePos = getIndexEndingWith( propNames, "-height" ); int familyPos = getIndexEndingWith( propNames, "-family" ); + int weightPos = getIndexEndingWith( propNames, "-weight" ); static const std::string sizeKeywords[] = { "xx-small", "x-small", "small", "medium", "large", "x-large", "xx-large", "xxx-large" }; @@ -1394,6 +1396,7 @@ void StyleSheetSpecification::registerDefaultShorthandParsers() { std::string sizeStr; std::string lineStr; std::string familyStr; + std::string weightStr; bool inLineHeight = false; for ( size_t i = 0; i < tokens.size(); i++ ) { @@ -1446,11 +1449,8 @@ void StyleSheetSpecification::registerDefaultShorthandParsers() { if ( isWeightWord( tok ) ) { std::string lt = String::toLower( tok ); - if ( lt != "normal" ) { - if ( !styleStr.empty() ) - styleStr += "|"; - styleStr += "bold"; - } + if ( lt != "normal" ) + weightStr = "bold"; continue; } @@ -1465,10 +1465,13 @@ void StyleSheetSpecification::registerDefaultShorthandParsers() { if ( !sizeStr.empty() ) { if ( stylePos != -1 && !styleStr.empty() ) properties.emplace_back( StyleSheetProperty( propNames[stylePos], styleStr ) ); + if ( weightPos != -1 && !weightStr.empty() ) + properties.emplace_back( StyleSheetProperty( propNames[weightPos], weightStr ) ); if ( sizePos != -1 ) properties.emplace_back( StyleSheetProperty( propNames[sizePos], sizeStr ) ); - if ( linePos != -1 && !lineStr.empty() ) - properties.emplace_back( StyleSheetProperty( propNames[linePos], lineStr ) ); + if ( linePos != -1 ) + properties.emplace_back( StyleSheetProperty( + propNames[linePos], lineStr.empty() ? "normal" : lineStr ) ); if ( familyPos != -1 && !familyStr.empty() ) { String::trimInPlace( familyStr ); if ( familyStr.size() >= 2 && diff --git a/src/eepp/ui/uirichtext.cpp b/src/eepp/ui/uirichtext.cpp index 0b4245677..91f8dfa21 100644 --- a/src/eepp/ui/uirichtext.cpp +++ b/src/eepp/ui/uirichtext.cpp @@ -941,6 +941,8 @@ void UIRichText::rebuildRichText( UILayout* container, RichText& richText, Intri UIWidget* widget = node->asType(); + bool handled = false; + if ( widget->isType( UI_TYPE_HTML_WIDGET ) && widget->asType()->isMergeable() ) { UITextSpan* span = widget->asType(); @@ -948,6 +950,14 @@ void UIRichText::rebuildRichText( UILayout* container, RichText& richText, Intri Rectf padding = span->getPixelsPadding(); bool hasOwnText = !span->getText().empty() && NULL != span->getFontStyleConfig().Font; + Float spanLineHeight = 0; + if ( span->isInlineBlock() ) { + auto& fontStyle = span->getFontStyleConfig(); + if ( fontStyle.Font ) + spanLineHeight = + (Float)fontStyle.Font->getFontHeight( fontStyle.CharacterSize ); + } + if ( hasOwnText ) { String::View spanText = span->getText().view(); @@ -969,7 +979,8 @@ void UIRichText::rebuildRichText( UILayout* container, RichText& richText, Intri spanText = spanText.substr( 1 ); if ( !spanText.empty() ) { - richText.addSpan( spanText, span->getFontStyleConfig(), margin, padding ); + richText.addSpan( spanText, span->getFontStyleConfig(), margin, padding, + spanLineHeight, span->isInlineBlock() ); if ( shouldCollapse ) lastSpanEndsWithSpace = spanText.back() == ' '; } @@ -994,68 +1005,80 @@ void UIRichText::rebuildRichText( UILayout* container, RichText& richText, Intri Rectf padRightOnly( 0, 0, padding.Right, padding.Bottom ); richText.addSpan( "", span->getFontStyleConfig(), rightOnly, padRightOnly ); } - } else if ( widget->isType( UI_TYPE_BR ) ) { - richText.addSpan( "\n", - widget->asType()->getRichText().getFontStyleConfig() ); - lastSpanEndsWithSpace = false; - } else { - Rectf margin = widget->getLayoutPixelsMargin(); - bool isBlock = widget->getLayoutWidthPolicy() == SizePolicy::MatchParent; - if ( widget->isType( UI_TYPE_HTML_WIDGET ) ) { - CSSDisplay display = widget->asType()->getDisplay(); - if ( display == CSSDisplay::Inline || display == CSSDisplay::InlineBlock ) - isBlock = false; - else if ( display == CSSDisplay::ListItem ) - isBlock = true; - } - if ( mode == IntrinsicMode::None ) { - if ( isBlock ) { - if ( container->getPixelsSize().getWidth() != 0 ) { - Float maxSize = eemax( 0.f, container->getPixelsSize().getWidth() - - container->getPixelsContentOffset().Left - - container->getPixelsContentOffset().Right - - margin.Left - margin.Right ); - widget->setPixelsSize( eemax( 0.f, maxSize ), - widget->getPixelsSize().getHeight() ); - } else { + handled = true; + } + + if ( !handled ) { + if ( widget->isType( UI_TYPE_BR ) ) { + richText.addSpan( + "\n", widget->asType()->getRichText().getFontStyleConfig() ); + lastSpanEndsWithSpace = false; + } else { + Rectf margin = widget->getLayoutPixelsMargin(); + bool isBlock = widget->getLayoutWidthPolicy() == SizePolicy::MatchParent; + if ( widget->isType( UI_TYPE_HTML_WIDGET ) ) { + CSSDisplay display = widget->asType()->getDisplay(); + if ( display == CSSDisplay::Inline || display == CSSDisplay::InlineBlock ) + isBlock = false; + else if ( display == CSSDisplay::ListItem ) + isBlock = true; + } + + if ( mode == IntrinsicMode::None ) { + if ( isBlock ) { + if ( container->getPixelsSize().getWidth() != 0 ) { + Float maxSize = + eemax( 0.f, container->getPixelsSize().getWidth() - + container->getPixelsContentOffset().Left - + container->getPixelsContentOffset().Right - + margin.Left - margin.Right ); + widget->setPixelsSize( eemax( 0.f, maxSize ), + widget->getPixelsSize().getHeight() ); + } else { + container->onAutoSizeChild( widget ); + } + } else if ( widget->getLayoutWidthPolicy() == SizePolicy::WrapContent || + widget->getLayoutHeightPolicy() == SizePolicy::WrapContent ) { container->onAutoSizeChild( widget ); } - } else if ( widget->getLayoutWidthPolicy() == SizePolicy::WrapContent || - widget->getLayoutHeightPolicy() == SizePolicy::WrapContent ) { - container->onAutoSizeChild( widget ); + + if ( widget->isType( UI_TYPE_TEXTSPAN ) && + widget->asType()->isInlineBlock() && + widget->getPixelsSize().getWidth() == 0 ) + widget->asType()->updateLayout(); } - } - Sizef size; - if ( mode == IntrinsicMode::Min ) { - size = Sizef( widget->getMinIntrinsicWidth(), 0 ); - } else if ( mode == IntrinsicMode::Max ) { - size = Sizef( widget->getMaxIntrinsicWidth(), 0 ); - } else { - size = widget->getPixelsSize(); - } + Sizef size; + if ( mode == IntrinsicMode::Min ) { + size = Sizef( widget->getMinIntrinsicWidth(), 0 ); + } else if ( mode == IntrinsicMode::Max ) { + size = Sizef( widget->getMaxIntrinsicWidth(), 0 ); + } else { + size = widget->getPixelsSize(); + } - Float w = size.getWidth(); - if ( isBlock && mode == IntrinsicMode::None && - container->getPixelsSize().getWidth() != 0 ) { - w = eemax( 0.f, container->getPixelsSize().getWidth() - - container->getPixelsContentOffset().Left - - container->getPixelsContentOffset().Right - margin.Left - - margin.Right ); - } + Float w = size.getWidth(); + if ( isBlock && mode == IntrinsicMode::None && + container->getPixelsSize().getWidth() != 0 ) { + w = eemax( 0.f, container->getPixelsSize().getWidth() - + container->getPixelsContentOffset().Left - + container->getPixelsContentOffset().Right - margin.Left - + margin.Right ); + } - CSSFloat floatType = CSSFloat::None; - CSSClear clearType = CSSClear::None; - if ( widget->isType( UI_TYPE_HTML_WIDGET ) ) { - floatType = widget->asType()->getCSSFloat(); - clearType = widget->asType()->getCSSClear(); - } + CSSFloat floatType = CSSFloat::None; + CSSClear clearType = CSSClear::None; + if ( widget->isType( UI_TYPE_HTML_WIDGET ) ) { + floatType = widget->asType()->getCSSFloat(); + clearType = widget->asType()->getCSSClear(); + } - richText.addCustomSize( Sizef( w + margin.Left + margin.Right, - size.getHeight() + margin.Top + margin.Bottom ), - isBlock, floatType, clearType ); - lastSpanEndsWithSpace = false; + richText.addCustomSize( Sizef( w + margin.Left + margin.Right, + size.getHeight() + margin.Top + margin.Bottom ), + isBlock, floatType, clearType ); + lastSpanEndsWithSpace = false; + } } }; @@ -1289,5 +1312,4 @@ void UIRichText::selCurEnd( const Int64& end ) { invalidateDraw(); } } - }} // namespace EE::UI diff --git a/src/eepp/ui/uitextspan.cpp b/src/eepp/ui/uitextspan.cpp index f3ea7e5f9..a23890d06 100644 --- a/src/eepp/ui/uitextspan.cpp +++ b/src/eepp/ui/uitextspan.cpp @@ -62,16 +62,29 @@ bool UITextSpan::isType( const Uint32& type ) const { } bool UITextSpan::isMergeable() const { - return mDisplay == CSSDisplay::Inline; + if ( mDisplay == CSSDisplay::Inline ) + return true; + if ( mDisplay == CSSDisplay::InlineBlock ) + return !getText().empty() && NULL != getFontStyleConfig().Font; + return false; +} + +bool UITextSpan::isInlineBlock() const { + return mDisplay == CSSDisplay::InlineBlock; } void UITextSpan::drawBorder() { if ( ( mFlags & UI_BORDER ) && NULL != mBorder ) { mBorder->setAlpha( mAlpha ); - mBorder->draw( { std::trunc( mScreenPos.x - mPaddingPx.Left ), - std::trunc( mScreenPos.y - mPaddingPx.Top ) }, - { std::floor( mSize.x + mPaddingPx.Left + mPaddingPx.Right ), - std::floor( mSize.y + mPaddingPx.Top + mPaddingPx.Bottom ) } ); + if ( isInlineBlock() ) { + mBorder->draw( Vector2f( std::trunc( mScreenPos.x ), std::trunc( mScreenPos.y ) ), + Sizef( std::floor( mSize.x ), std::floor( mSize.y ) ) ); + } else { + mBorder->draw( { std::trunc( mScreenPos.x - mPaddingPx.Left ), + std::trunc( mScreenPos.y - mPaddingPx.Top ) }, + { std::floor( mSize.x + mPaddingPx.Left + mPaddingPx.Right ), + std::floor( mSize.y + mPaddingPx.Top + mPaddingPx.Bottom ) } ); + } } } diff --git a/src/tests/unit_tests/uihtml_tests.cpp b/src/tests/unit_tests/uihtml_tests.cpp index 45ee02fd3..cfae2bdf3 100644 --- a/src/tests/unit_tests/uihtml_tests.cpp +++ b/src/tests/unit_tests/uihtml_tests.cpp @@ -209,6 +209,86 @@ UTEST( UIRichText, spanPadding ) { Engine::destroySingleton(); } +UTEST( UIRichText, anchorPadding ) { + auto win = Engine::instance()->createWindow( + WindowSettings( 800, 600, "Anchor Span Padding Test", WindowStyle::Default, + WindowBackend::Default, 32, {}, 1, false, true ), + ContextSettings( false, 0, 0, GLv_default, true, false ) ); + FileSystem::changeWorkingDirectory( Sys::getProcessPath() ); + + FontTrueType* font = FontTrueType::New( "NotoSans-Regular" ); + font->loadFromFile( "../assets/fonts/NotoSans-Regular.ttf" ); + ASSERT_TRUE( font != nullptr && font->loaded() ); + FontFamily::loadFromRegular( font ); + + UI::UISceneNode* sceneNode = UI::UISceneNode::New(); + SceneManager::instance()->add( sceneNode ); + UI::UIThemeManager* themeManager = sceneNode->getUIThemeManager(); + themeManager->setDefaultFont( font ); + sceneNode->setURI( "file://" + Sys::getProcessPath() + "assets/html/" ); + std::string html; + FileSystem::fileGet( "assets/html/anchor_padding.html", html ); + sceneNode->loadLayoutFromString( HTMLFormatter::HTMLtoXML( html ) ); + win->setClearColor( Color::White ); + + win->getInput()->update(); + SceneManager::instance()->update(); + + win->clear(); + SceneManager::instance()->draw(); + win->display(); + + compareImages( utest_state, utest_result, win, "eepp-ui-anchor-padding", "html" ); + + auto anchors = sceneNode->getRoot()->findAllByTag( "a" ); + ASSERT_GE( anchors.size(), (size_t)1 ); + auto downloadLink = anchors[0]->asType(); + EXPECT_NEAR( downloadLink->getPixelsSize().getWidth(), 81.f, 3.f ); + EXPECT_NEAR( downloadLink->getPixelsSize().getHeight(), 28.f, 3.f ); + + Engine::destroySingleton(); +} + +UTEST( UIRichText, anchorPaddingLineHeight ) { + auto win = Engine::instance()->createWindow( + WindowSettings( 800, 600, "Anchor Padding LineHeight Test", WindowStyle::Default, + WindowBackend::Default, 32, {}, 1, false, true ), + ContextSettings( false, 0, 0, GLv_default, true, false ) ); + FileSystem::changeWorkingDirectory( Sys::getProcessPath() ); + + FontTrueType* font = FontTrueType::New( "NotoSans-Regular" ); + font->loadFromFile( "../assets/fonts/NotoSans-Regular.ttf" ); + ASSERT_TRUE( font != nullptr && font->loaded() ); + FontFamily::loadFromRegular( font ); + + UI::UISceneNode* sceneNode = UI::UISceneNode::New(); + SceneManager::instance()->add( sceneNode ); + UI::UIThemeManager* themeManager = sceneNode->getUIThemeManager(); + themeManager->setDefaultFont( font ); + sceneNode->setURI( "file://" + Sys::getProcessPath() + "assets/html/" ); + std::string html; + FileSystem::fileGet( "assets/html/anchor_padding_lineheight.html", html ); + sceneNode->loadLayoutFromString( HTMLFormatter::HTMLtoXML( html ) ); + win->setClearColor( Color::White ); + + win->getInput()->update(); + SceneManager::instance()->update(); + + win->clear(); + SceneManager::instance()->draw(); + win->display(); + + compareImages( utest_state, utest_result, win, "eepp-ui-anchor-padding-lineheight", "html" ); + + auto anchors = sceneNode->getRoot()->findAllByTag( "a" ); + ASSERT_GE( anchors.size(), (size_t)1 ); + auto downloadLink = anchors[0]->asType(); + EXPECT_NEAR( downloadLink->getPixelsSize().getWidth(), 81.f, 3.f ); + EXPECT_NEAR( downloadLink->getPixelsSize().getHeight(), 28.f, 3.f ); + + Engine::destroySingleton(); +} + UTEST( UIHTMLTable, complexLayout3 ) { auto win = Engine::instance()->createWindow( WindowSettings( 1024, 650, "HTML Tables Test 3", WindowStyle::Default, @@ -1391,3 +1471,47 @@ UTEST( UIBackground, imageAtlasPositioningPixelDensity2 ) { Engine::destroySingleton(); EE::Graphics::PixelDensity::setPixelDensity( 1.0f ); } + +UTEST( UIBackground, InlineBlockImageSpans ) { + auto win = Engine::instance()->createWindow( + WindowSettings( 1024, 653, "inline-block image spans", WindowStyle::Default, + WindowBackend::Default, 32, {}, 1, false, true ), + ContextSettings( false, 0, 0, GLv_default, true, false ) ); + FileSystem::changeWorkingDirectory( Sys::getProcessPath() ); + + UI::UISceneNode* sceneNode = init_test_inline_block(); + + sceneNode->setURI( "file://" + Sys::getProcessPath() + "assets/html/" ); + + std::string html; + FileSystem::fileGet( "assets/html/inline_block.html", html ); + sceneNode->loadLayoutFromString( HTMLFormatter::HTMLtoXML( html ) ); + win->setClearColor( Color::White ); + + win->getInput()->update(); + SceneManager::instance()->update(); + + win->clear(); + SceneManager::instance()->draw(); + win->display(); + + auto anchors = sceneNode->getRoot()->findAllByTag( "a" ); + auto spans = sceneNode->getRoot()->querySelectorAll( "a > span" ); + + EXPECT_GT( anchors.size(), (size_t)0 ); + EXPECT_GT( spans.size(), (size_t)0 ); + + for ( auto anchor : anchors ) { + EXPECT_GT( anchor->getPixelsSize().getWidth(), 0 ); + EXPECT_GT( anchor->getPixelsSize().getHeight(), 0 ); + } + + for ( auto span : spans ) { + EXPECT_GT( span->getPixelsSize().getWidth(), 0 ); + EXPECT_GT( span->getPixelsSize().getHeight(), 0 ); + } + + compareImages( utest_state, utest_result, win, "eepp-ui-inline-block-image-spans", "html", 4 ); + + Engine::destroySingleton(); +}