diff --git a/bin/unit_tests/assets/fontrendering/eepp-text-background-color.webp b/bin/unit_tests/assets/fontrendering/eepp-text-background-color.webp new file mode 100644 index 000000000..d8eb27da9 Binary files /dev/null and b/bin/unit_tests/assets/fontrendering/eepp-text-background-color.webp differ diff --git a/include/eepp/graphics/fontstyleconfig.hpp b/include/eepp/graphics/fontstyleconfig.hpp index 7d26b7954..6ff6bd383 100644 --- a/include/eepp/graphics/fontstyleconfig.hpp +++ b/include/eepp/graphics/fontstyleconfig.hpp @@ -29,13 +29,15 @@ class FontStyleConfig { const Vector2f& getFontShadowOffset() const { return ShadowOffset; } + const Color& getBackgroundColor() const { return BackgroundColor; } + FontStyleConfig() {} bool operator==( const FontStyleConfig& other ) { return Font == other.Font && CharacterSize == other.CharacterSize && Style == other.Style && FontColor == other.FontColor && ShadowColor == other.ShadowColor && ShadowOffset == other.ShadowOffset && OutlineThickness == other.OutlineThickness && - OutlineColor == other.OutlineColor; + OutlineColor == other.OutlineColor && BackgroundColor == other.BackgroundColor; } bool operator!=( const FontStyleConfig& other ) { return !( *this == other ); } @@ -49,6 +51,7 @@ class FontStyleConfig { ShadowOffset = fontStyleConfig.ShadowOffset; OutlineThickness = fontStyleConfig.OutlineThickness; OutlineColor = fontStyleConfig.OutlineColor; + BackgroundColor = fontStyleConfig.BackgroundColor; } Graphics::Font* Font{ nullptr }; @@ -59,6 +62,7 @@ class FontStyleConfig { Vector2f ShadowOffset{ PixelDensity::dpToPx( 1 ), PixelDensity::dpToPx( 1 ) }; Float OutlineThickness{ 0 }; Color OutlineColor{ 0, 0, 0, 255 }; + Color BackgroundColor{ Color::Transparent }; }; }} // namespace EE::Graphics diff --git a/include/eepp/graphics/richtext.hpp b/include/eepp/graphics/richtext.hpp index 7ffbb8775..ffbec00cc 100644 --- a/include/eepp/graphics/richtext.hpp +++ b/include/eepp/graphics/richtext.hpp @@ -41,7 +41,8 @@ class EE_API RichText : public Drawable { * @param style The text style (optional, uses default if Regular). */ void addSpan( const String& text, Font* font = nullptr, Uint32 characterSize = 0, - Color color = Color::White, Uint32 style = Text::Regular ); + Color color = Color::White, Uint32 style = Text::Regular, + Color backgroundColor = Color::Transparent ); /** @brief Clears all text spans. */ void clear(); diff --git a/include/eepp/graphics/text.hpp b/include/eepp/graphics/text.hpp index 83209aa96..c80ba0976 100644 --- a/include/eepp/graphics/text.hpp +++ b/include/eepp/graphics/text.hpp @@ -400,7 +400,6 @@ class EE_API Text { String mString; ///< String to display FontStyleConfig mFontStyleConfig; - Color mBackgroundColor{ Color::Transparent }; mutable Rectf mBounds; ///< Bounding rectangle of the text (in local coordinates) mutable bool mGeometryNeedUpdate : 1 { false }; ///< Does the geometry need to be recomputed? diff --git a/include/eepp/ui/uirichtext.hpp b/include/eepp/ui/uirichtext.hpp index 2b4f28efd..385914cd0 100644 --- a/include/eepp/ui/uirichtext.hpp +++ b/include/eepp/ui/uirichtext.hpp @@ -63,6 +63,10 @@ class EE_API UIRichText : public UILayout { UIRichText* setFontColor( const Color& color ); + const Color& getFontBackgroundColor() const; + + UIRichText* setFontBackgroundColor( const Color& color ); + const Color& getFontShadowColor() const; UIRichText* setFontShadowColor( const Color& color ); diff --git a/include/eepp/ui/uitextspan.hpp b/include/eepp/ui/uitextspan.hpp index a8902d21f..0b2eb32a9 100644 --- a/include/eepp/ui/uitextspan.hpp +++ b/include/eepp/ui/uitextspan.hpp @@ -77,6 +77,10 @@ class EE_API UITextSpan : public UIWidget { UITextSpan* setFontColor( const Color& color ); + const Color& getFontBackgroundColor() const; + + UITextSpan* setFontBackgroundColor( const Color& color ); + const Color& getFontShadowColor() const; UITextSpan* setFontShadowColor( const Color& color ); @@ -97,6 +101,7 @@ class EE_API UITextSpan : public UIWidget { StyleStateOutlineColor = 1 << 5, StyleStateFontShadowColor = 1 << 6, StyleStateFontShadowOffset = 1 << 7, + StyleStateFontBackgroundColor = 1 << 8, StyleStateAll = 0xFFFFFFFF }; @@ -108,6 +113,7 @@ class EE_API UITextSpan : public UIWidget { bool hasOutlineColor() const; bool hasFontShadowColor() const; bool hasFontShadowOffset() const; + bool hasFontBackgroundColor() const; protected: Uint32 mStyleState{ StyleStateNone }; diff --git a/src/eepp/graphics/linewrap.cpp b/src/eepp/graphics/linewrap.cpp index 1520aa810..77d981ee1 100644 --- a/src/eepp/graphics/linewrap.cpp +++ b/src/eepp/graphics/linewrap.cpp @@ -168,18 +168,20 @@ LineWrap::computeLineBreaksInternal( const String::View& string, Font* font, Uin ? font->getGlyph( curChar, characterSize, bold, italic, outlineThickness ).advance : hspace; + Float kerning = 0; if ( curChar == '\t' ) { w = Text::tabAdvance( hspace, tabWidth, tabStops ? xoffset : std::optional{} ); prevChar = 0; } else if ( !isMonospace && curChar != '\r' ) { if ( !( textDrawHints & TextHints::NoKerning ) ) { - w += font->getKerning( prevChar, curChar, characterSize, bold, italic, - outlineThickness ); + kerning = font->getKerning( prevChar, curChar, characterSize, bold, italic, + outlineThickness ); } prevChar = curChar; } - xoffset += w; + Float xpos = xoffset + kerning; + xoffset = xpos + w; if ( hasWrap && xoffset > maxWidth ) { if ( mode == LineWrapMode::Word && lastSpace != std::string::npos ) { @@ -188,7 +190,15 @@ LineWrap::computeLineBreaksInternal( const String::View& string, Font* font, Uin } info.wraps.push_back( lastSpace + 1 ); - xoffset = info.paddingStart + ( xoffset - lastWidth ); + + Float startKerning = 0; + if ( !( textDrawHints & TextHints::NoKerning ) && !isMonospace && + lastSpace + 1 < string.size() ) { + startKerning = font->getKerning( string[lastSpace], string[lastSpace + 1], + characterSize, bold, italic, outlineThickness ); + } + + xoffset = info.paddingStart + ( xoffset - lastWidth - startKerning ); } else { // If we are about to split a word, check if we can move it to the next line if ( mode == LineWrapMode::Word && info.wraps.size() == 1 && @@ -213,6 +223,7 @@ LineWrap::computeLineBreaksInternal( const String::View& string, Font* font, Uin info.wraps.push_back( idx ); xoffset = info.paddingStart; + prevChar = 0; } } lastSpace = std::string::npos; diff --git a/src/eepp/graphics/richtext.cpp b/src/eepp/graphics/richtext.cpp index 2c7e4a515..0f575686f 100644 --- a/src/eepp/graphics/richtext.cpp +++ b/src/eepp/graphics/richtext.cpp @@ -98,7 +98,7 @@ void RichText::addCustomSize( const Sizef& size ) { } void RichText::addSpan( const String& text, Font* font, Uint32 characterSize, Color color, - Uint32 style ) { + Uint32 style, Color backgroundColor ) { FontStyleConfig config; config.Font = font ? font : mDefaultStyle.Font; config.CharacterSize = characterSize != 0 ? characterSize : mDefaultStyle.CharacterSize; @@ -108,6 +108,7 @@ void RichText::addSpan( const String& text, Font* font, Uint32 characterSize, Co config.ShadowOffset = mDefaultStyle.ShadowOffset; config.OutlineThickness = mDefaultStyle.OutlineThickness; config.OutlineColor = mDefaultStyle.OutlineColor; + config.BackgroundColor = backgroundColor; addSpan( text, config ); } diff --git a/src/eepp/graphics/text.cpp b/src/eepp/graphics/text.cpp index 528b08173..644944654 100644 --- a/src/eepp/graphics/text.cpp +++ b/src/eepp/graphics/text.cpp @@ -1575,11 +1575,11 @@ const Uint32& Text::getTabWidth() const { } Color Text::getBackgroundColor() const { - return mBackgroundColor; + return mFontStyleConfig.BackgroundColor; } void Text::setBackgroundColor( const Color& backgroundColor ) { - mBackgroundColor = backgroundColor; + mFontStyleConfig.BackgroundColor = backgroundColor; } const Vector2f& Text::getShadowOffset() const { @@ -1629,11 +1629,11 @@ void Text::draw( const Float& X, const Float& Y, const Vector2f& scale, const Fl BlendMode effect, const OriginPoint& rotationCenter, const OriginPoint& scaleCenter, const std::vector& colors, const std::vector& outlineColors, const Color& backgroundColor ) { - unsigned int numvert = mVertices.size(); - - if ( 0 == numvert ) + if ( NULL == mFontStyleConfig.Font || mString.empty() ) return; + unsigned int numvert = mVertices.size(); + GlobalBatchRenderer::instance()->draw(); if ( rotation != 0.0f || scale != 1.0f ) { @@ -1670,7 +1670,32 @@ void Text::draw( const Float& X, const Float& Y, const Vector2f& scale, const Fl Primitives p; p.setForceDraw( true ); p.setColor( backgroundColor ); - p.drawRectangle( getLocalBounds() ); + ensureVisualLinesUpdate(); + Float vspace = getLineSpacing(); + for ( size_t i = 0; i < mVisualLines.size(); ++i ) { + Float centerDiffX = 0; + if ( i < mLinesWidth.size() ) { + switch ( Font::getHorizontalAlign( mAlign ) ) { + case TEXT_ALIGN_CENTER: + centerDiffX = std::trunc( ( mCachedWidth - mLinesWidth[i] ) * 0.5f ); + break; + case TEXT_ALIGN_RIGHT: + centerDiffX = mCachedWidth - mLinesWidth[i]; + break; + } + } + p.drawRectangle( Rectf( centerDiffX, i * vspace, centerDiffX + mLinesWidth[i], + ( i + 1 ) * vspace ) ); + } + } + + if ( 0 == numvert ) { + if ( rotation != 0.0f || scale != 1.0f ) { + GLi->popMatrix(); + } else { + GLi->translatef( -X, -Y, 0 ); + } + return; } Texture* texture = mFontStyleConfig.Font->getTexture( mFontStyleConfig.CharacterSize ); @@ -1744,7 +1769,7 @@ void Text::draw( const Float& X, const Float& Y, const Vector2f& scale, const Fl } draw( X, Y, scale, rotation, effect, rotationCenter, scaleCenter, mColors, mOutlineColors, - mBackgroundColor ); + mFontStyleConfig.BackgroundColor ); } void Text::ensureGeometryUpdate() { @@ -2330,6 +2355,7 @@ void Text::setStyleConfig( const FontStyleConfig& styleConfig ) { setOutlineColor( styleConfig.OutlineColor ); setShadowColor( styleConfig.ShadowColor ); setShadowOffset( styleConfig.ShadowOffset ); + setBackgroundColor( styleConfig.BackgroundColor ); } bool Text::hasSameFontStyleConfig( const FontStyleConfig& styleConfig ) { diff --git a/src/eepp/graphics/textlayout.cpp b/src/eepp/graphics/textlayout.cpp index 23ea80a14..41579a368 100644 --- a/src/eepp/graphics/textlayout.cpp +++ b/src/eepp/graphics/textlayout.cpp @@ -620,11 +620,18 @@ void TextLayout::wrapLayout( const String::View& string, TextLayout& result, breakStringIdx = string.size(); ShapedGlyph& breakGlyph = sp.shapedGlyphs[breakIndex]; + Float breakPos = breakGlyph.position.x; + Float kerning = 0; + if ( breakIndex > 0 ) { + ShapedGlyph& prevBreakGlyph = sp.shapedGlyphs[breakIndex - 1]; + kerning = ( prevBreakGlyph.position.x + prevBreakGlyph.advance.x ) - + breakGlyph.position.x; + } sp.wrapInfo.wraps.push_back( breakStringIdx ); - sp.wrapInfo.wrapsWidth.push_back( std::ceil( breakGlyph.position.x ) ); + sp.wrapInfo.wrapsWidth.push_back( std::ceil( breakPos + kerning ) ); - Vector2f adjustment( -breakGlyph.position.x + sp.wrapInfo.paddingStart, vspace ); + Vector2f adjustment( sp.wrapInfo.paddingStart - breakGlyph.position.x, vspace ); for ( std::size_t k = breakIndex; k <= idx; ++k ) sp.shapedGlyphs[k].position += adjustment; diff --git a/src/eepp/ui/uirichtext.cpp b/src/eepp/ui/uirichtext.cpp index f9ab2bbc7..73cfbc9b5 100644 --- a/src/eepp/ui/uirichtext.cpp +++ b/src/eepp/ui/uirichtext.cpp @@ -98,6 +98,9 @@ bool UIRichText::applyProperty( const StyleSheetProperty& attribute ) { case PropertyId::Color: setFontColor( attribute.asColor() ); break; + case PropertyId::BackgroundColor: + setFontBackgroundColor( attribute.asColor() ); + break; case PropertyId::TextShadowColor: setFontShadowColor( attribute.asColor() ); break; @@ -141,6 +144,8 @@ std::string UIRichText::getPropertyString( const PropertyDefinition* propertyDef return Graphics::Text::styleFlagToString( getFontStyle() ); case PropertyId::Color: return getFontColor().toHexString(); + case PropertyId::BackgroundColor: + return getFontBackgroundColor().toHexString(); case PropertyId::TextShadowColor: return getFontShadowColor().toHexString(); case PropertyId::TextShadowOffset: @@ -161,10 +166,10 @@ std::string UIRichText::getPropertyString( const PropertyDefinition* propertyDef std::vector UIRichText::getPropertiesImplemented() const { auto props = UILayout::getPropertiesImplemented(); - auto local = { - PropertyId::FontFamily, PropertyId::FontSize, PropertyId::FontStyle, - PropertyId::Color, PropertyId::TextShadowColor, PropertyId::TextShadowOffset, - PropertyId::TextStrokeWidth, PropertyId::TextStrokeColor, PropertyId::TextAlign }; + auto local = { PropertyId::FontFamily, PropertyId::FontSize, PropertyId::FontStyle, + PropertyId::Color, PropertyId::BackgroundColor, PropertyId::TextShadowColor, + PropertyId::TextShadowOffset, PropertyId::TextStrokeWidth, PropertyId::TextStrokeColor, + PropertyId::TextAlign }; props.insert( props.end(), local.begin(), local.end() ); return props; } @@ -229,6 +234,19 @@ UIRichText* UIRichText::setFontColor( const Color& color ) { return this; } +const Color& UIRichText::getFontBackgroundColor() const { + return mRichText.getFontStyleConfig().BackgroundColor; +} + +UIRichText* UIRichText::setFontBackgroundColor( const Color& color ) { + if ( mRichText.getFontStyleConfig().BackgroundColor != color ) { + mRichText.getFontStyleConfig().BackgroundColor = color; + mRichText.invalidate(); + updateDefaultSpansStyle(); + } + return this; +} + const Color& UIRichText::getFontShadowColor() const { return mRichText.getFontStyleConfig().ShadowColor; } diff --git a/src/eepp/ui/uitextspan.cpp b/src/eepp/ui/uitextspan.cpp index ecf9abd1f..1870675b1 100644 --- a/src/eepp/ui/uitextspan.cpp +++ b/src/eepp/ui/uitextspan.cpp @@ -63,6 +63,9 @@ bool UITextSpan::applyProperty( const StyleSheetProperty& attribute ) { case PropertyId::Color: setFontColor( attribute.asColor() ); break; + case PropertyId::BackgroundColor: + setFontBackgroundColor( attribute.asColor() ); + break; case PropertyId::TextShadowColor: setFontShadowColor( attribute.asColor() ); break; @@ -112,6 +115,8 @@ std::string UITextSpan::getPropertyString( const PropertyDefinition* propertyDef return Graphics::Text::styleFlagToString( getFontStyle() ); case PropertyId::Color: return getFontColor().toHexString(); + case PropertyId::BackgroundColor: + return getFontBackgroundColor().toHexString(); case PropertyId::TextShadowColor: return getFontShadowColor().toHexString(); case PropertyId::TextShadowOffset: @@ -133,6 +138,7 @@ std::vector UITextSpan::getPropertiesImplemented() const { PropertyId::FontSize, PropertyId::FontStyle, PropertyId::Color, + PropertyId::BackgroundColor, PropertyId::TextShadowColor, PropertyId::TextShadowOffset, PropertyId::TextStrokeWidth, @@ -248,6 +254,19 @@ UITextSpan* UITextSpan::setFontColor( const Color& color ) { return this; } +const Color& UITextSpan::getFontBackgroundColor() const { + return mFontStyleConfig.getBackgroundColor(); +} + +UITextSpan* UITextSpan::setFontBackgroundColor( const Color& color ) { + if ( mFontStyleConfig.BackgroundColor != color ) { + mFontStyleConfig.BackgroundColor = color; + mStyleState |= StyleStateFontBackgroundColor; + onFontStyleChanged(); + } + return this; +} + const Color& UITextSpan::getFontShadowColor() const { return mFontStyleConfig.getFontShadowColor(); } @@ -404,6 +423,12 @@ void UITextSpan::setInheritedStyle( const UIFontStyleConfig& fontStyleConfig ) { fontStyleChanged = true; } + if ( !hasFontBackgroundColor() && + mFontStyleConfig.BackgroundColor != fontStyleConfig.BackgroundColor ) { + mFontStyleConfig.BackgroundColor = fontStyleConfig.BackgroundColor; + fontStyleChanged = true; + } + if ( fontChanged ) onFontChanged(); @@ -454,4 +479,8 @@ bool UITextSpan::hasFontShadowOffset() const { return 0 != ( mStyleState & StyleStateFontShadowOffset ); } +bool UITextSpan::hasFontBackgroundColor() const { + return 0 != ( mStyleState & StyleStateFontBackgroundColor ); +} + }} // namespace EE::UI diff --git a/src/tests/unit_tests/fontrendering.cpp b/src/tests/unit_tests/fontrendering.cpp index 336528058..c84096327 100644 --- a/src/tests/unit_tests/fontrendering.cpp +++ b/src/tests/unit_tests/fontrendering.cpp @@ -1434,3 +1434,69 @@ UTEST( FontRendering, TextContiguousOffset ) { runTest(); } } + +UTEST( FontRendering, TextBackgroundColor ) { + FileSystem::changeWorkingDirectory( Sys::getProcessPath() ); + + const auto runTest = [&]() { + auto win = Engine::instance()->createWindow( + WindowSettings( 512, 400, "eepp - Text Background Color", WindowStyle::Default, + WindowBackend::Default, 32, {}, 1, false, true ) ); + + ASSERT_TRUE_MSG( win->isOpen(), "Failed to create Window" ); + + win->setClearColor( RGB( 255, 255, 255 ) ); + win->clear(); + + FontTrueType* font = FontTrueType::New( "NotoSans-Regular" ); + font->loadFromFile( "../assets/fonts/NotoSans-Regular.ttf" ); + + Vector2f pos{ 20, 20 }; + Text text; + text.setFont( font ); + text.setFontSize( 20 ); + text.setFillColor( Color::Black ); + text.setBackgroundColor( Color::Yellow ); + text.setString( "Text with background color\nand multiple lines." ); + text.draw( pos.x, pos.y ); + + pos.y += text.getTextHeight() + 20; + text.setAlign( TEXT_ALIGN_CENTER ); + text.setString( "Centered text with\nbackground color." ); + text.draw( pos.x, pos.y ); + + pos.y += text.getTextHeight() + 20; + text.setAlign( TEXT_ALIGN_LEFT ); + text.setLineWrapMode( LineWrapMode::Word ); + text.setMaxWrapWidth( 200 ); + text.setString( + "Wrapped text with background color that should only cover the text area." ); + text.draw( pos.x, pos.y ); + + pos.y += text.getTextHeight() + 20; + text.setLineWrapMode( LineWrapMode::NoWrap ); + text.setBackgroundColor( Color::cyan ); + text.setString( " " ); // Only spaces + text.draw( pos.x, pos.y ); + + compareImages( utest_state, utest_result, win, "eepp-text-background-color" ); + + Engine::destroySingleton(); + }; + + UTEST_PRINT_STEP( "Text Shaper disabled" ); + { + BoolScopedOp op( Text::TextShaperEnabled, false ); + runTest(); + } + + UTEST_PRINT_STEP( "Text Shaper enabled" ); + { + BoolScopedOp op( Text::TextShaperEnabled, true ); + runTest(); + + UTEST_PRINT_STEP( "Text Shaper enabled w/o optimizations" ); + BoolScopedOp op2( Text::TextShaperOptimizations, false ); + runTest(); + } +}