diff --git a/bin/unit_tests/assets/html/eepp-ui-anchor-margins.webp b/bin/unit_tests/assets/html/eepp-ui-anchor-margins.webp
index ac9873391..f501815a6 100644
Binary files a/bin/unit_tests/assets/html/eepp-ui-anchor-margins.webp and b/bin/unit_tests/assets/html/eepp-ui-anchor-margins.webp differ
diff --git a/bin/unit_tests/assets/html/eepp-ui-span-padding.webp b/bin/unit_tests/assets/html/eepp-ui-span-padding.webp
index 63545e563..2fcadba2b 100644
Binary files a/bin/unit_tests/assets/html/eepp-ui-span-padding.webp and b/bin/unit_tests/assets/html/eepp-ui-span-padding.webp differ
diff --git a/include/eepp/graphics/richtext.hpp b/include/eepp/graphics/richtext.hpp
index e746b8fd5..84a08c182 100644
--- a/include/eepp/graphics/richtext.hpp
+++ b/include/eepp/graphics/richtext.hpp
@@ -155,6 +155,19 @@ class EE_API RichText : public Drawable {
/** @return The list of rendered lines. */
const std::vector& getLines() const { return mLines; }
+ /** Sets line-height as a multiplier of the font's default line spacing.
+ * Use 0 to reset to font default. */
+ void setLineHeight( Float height );
+
+ /** @return The line height multiplier. */
+ Float getLineHeight() const { return mLineHeight; }
+
+ /** Sets text-indent (pixels) for the first line. */
+ void setTextIndent( Float indent );
+
+ /** @return The text indent in pixels. */
+ Float getTextIndent() const { return mTextIndent; }
+
/** @brief Sets the text selection range. */
void setSelection( TextSelectionRange range );
@@ -207,6 +220,8 @@ class EE_API RichText : public Drawable {
Sizef mSize;
Int64 mTotalCharacterCount{ 0 };
bool mNeedsLayoutUpdate{ true };
+ Float mLineHeight{ 0 };
+ Float mTextIndent{ 0 };
};
}} // namespace EE::Graphics
diff --git a/include/eepp/ui/css/propertydefinition.hpp b/include/eepp/ui/css/propertydefinition.hpp
index f60eddee0..a82a3a822 100644
--- a/include/eepp/ui/css/propertydefinition.hpp
+++ b/include/eepp/ui/css/propertydefinition.hpp
@@ -220,6 +220,8 @@ enum class PropertyId : Uint32 {
TextAsFallback = String::hash( "text-as-fallback" ),
SelectOnClick = String::hash( "select-on-click" ),
LineSpacing = String::hash( "line-spacing" ),
+ LineHeight = String::hash( "line-height" ),
+ TextIndent = String::hash( "text-indent" ),
GravityOwner = String::hash( "gravity-owner" ),
Href = String::hash( "href" ),
Focusable = String::hash( "focusable" ),
diff --git a/include/eepp/ui/uirichtext.hpp b/include/eepp/ui/uirichtext.hpp
index 59e6b07d4..1302d66a3 100644
--- a/include/eepp/ui/uirichtext.hpp
+++ b/include/eepp/ui/uirichtext.hpp
@@ -113,6 +113,14 @@ class EE_API UIRichText : public UIHTMLWidget {
UIRichText* setTextAlign( const Uint32& align );
+ Float getLineHeightPx() const { return mLineHeightPx; }
+
+ UIRichText* setLineHeightPx( Float height );
+
+ Float getTextIndentPx() const { return mTextIndentPx; }
+
+ UIRichText* setTextIndentPx( Float indent );
+
bool isTextSelectionEnabled() const;
void setTextSelectionEnabled( bool active );
@@ -138,6 +146,8 @@ class EE_API UIRichText : public UIHTMLWidget {
Int64 mSelCurInit{ 0 };
Int64 mSelCurEnd{ 0 };
bool mSelecting{ false };
+ Float mLineHeightPx{ 0 };
+ Float mTextIndentPx{ 0 };
explicit UIRichText( const std::string& tag = "richtext" );
diff --git a/src/eepp/graphics/richtext.cpp b/src/eepp/graphics/richtext.cpp
index e0c458ffe..00a2873a3 100644
--- a/src/eepp/graphics/richtext.cpp
+++ b/src/eepp/graphics/richtext.cpp
@@ -130,6 +130,20 @@ void RichText::setSelection( TextSelectionRange range ) {
}
}
+void RichText::setLineHeight( Float height ) {
+ if ( mLineHeight != height ) {
+ mLineHeight = height;
+ invalidate();
+ }
+}
+
+void RichText::setTextIndent( Float indent ) {
+ if ( mTextIndent != indent ) {
+ mTextIndent = indent;
+ invalidate();
+ }
+}
+
void RichText::setSelectionColor( const Color& color ) {
mSelectionColor = color;
}
@@ -466,7 +480,7 @@ void RichText::updateLayout() {
mLines.clear();
mLines.push_back( RenderParagraph() );
- Float curX = 0;
+ Float curX = mTextIndent;
Float maxWidth = 0;
Int64 curCharIdx = 0;
@@ -524,7 +538,9 @@ void RichText::updateLayout() {
renderSpanText->setStyleConfig( fontStyle );
Float ascent = fontStyle.Font->getAscent( fontStyle.CharacterSize );
- Float height = fontStyle.Font->getLineSpacing( fontStyle.CharacterSize );
+ Float height = mLineHeight > 0
+ ? mLineHeight
+ : fontStyle.Font->getLineSpacing( fontStyle.CharacterSize );
Float spanWidth = renderSpanText->getTextWidth();
RenderSpan renderSpan;
@@ -686,7 +702,7 @@ void RichText::updateLayout() {
mLines.clear();
mLines.push_back( RenderParagraph() );
- Float curX = 0;
+ Float curX = mTextIndent;
Float maxWidth = 0;
Int64 curCharIdx = 0;
@@ -805,7 +821,9 @@ void RichText::updateLayout() {
renderSpanText->setStyleConfig( fontStyle );
Float ascent = fontStyle.Font->getAscent( fontStyle.CharacterSize );
- Float height = fontStyle.Font->getLineSpacing( fontStyle.CharacterSize );
+ Float height = mLineHeight > 0
+ ? mLineHeight
+ : fontStyle.Font->getLineSpacing( fontStyle.CharacterSize );
Float spanWidth = renderSpanText->getTextWidth();
RenderSpan renderSpan;
diff --git a/src/eepp/ui/css/stylesheetspecification.cpp b/src/eepp/ui/css/stylesheetspecification.cpp
index 8d322d526..9420e8900 100644
--- a/src/eepp/ui/css/stylesheetspecification.cpp
+++ b/src/eepp/ui/css/stylesheetspecification.cpp
@@ -229,6 +229,8 @@ void StyleSheetSpecification::registerDefaultProperties() {
registerProperty( "font-style", "", true ).addAlias( "font-weight" );
registerProperty( "text-decoration", "", true );
registerProperty( "line-spacing", "", true ).setType( PropertyType::NumberLength );
+ registerProperty( "line-height", "", true ).setType( PropertyType::NumberLength );
+ registerProperty( "text-indent", "", true ).setType( PropertyType::NumberLength );
registerProperty( "text-stroke-width", "", true )
.setType( PropertyType::NumberLength )
.addAlias( "fontoutlinethickness" );
diff --git a/src/eepp/ui/uirichtext.cpp b/src/eepp/ui/uirichtext.cpp
index 61550af58..9dde96bb3 100644
--- a/src/eepp/ui/uirichtext.cpp
+++ b/src/eepp/ui/uirichtext.cpp
@@ -316,6 +316,33 @@ bool UIRichText::applyProperty( const StyleSheetProperty& attribute ) {
mDataProperties["data-language"] = attribute;
break;
}
+ case PropertyId::LineHeight: {
+ std::string val = attribute.value();
+ if ( val == "normal" || val.empty() ) {
+ setLineHeightPx( 0 );
+ } else {
+ // Unitless number: multiplier of font size
+ bool isUnitless = !val.empty();
+ for ( char c : val ) {
+ if ( c != '-' && c != '+' && c != '.' && !String::isNumber( c, false ) ) {
+ isUnitless = false;
+ break;
+ }
+ }
+
+ if ( isUnitless ) {
+ Float multiplier = StyleSheetLength::fromString( val, 0 ).getValue();
+ setLineHeightPx( multiplier * getFontSize() );
+ } else {
+ setLineHeightPx( lengthFromValue( attribute ) );
+ }
+ }
+ break;
+ }
+ case PropertyId::TextIndent: {
+ setTextIndentPx( lengthFromValue( attribute ) );
+ break;
+ }
default:
return UIHTMLWidget::applyProperty( attribute );
}
@@ -360,6 +387,10 @@ std::string UIRichText::getPropertyString( const PropertyDefinition* propertyDef
return getTextAlign() == TEXT_ALIGN_CENTER
? "center"
: ( getTextAlign() == TEXT_ALIGN_RIGHT ? "right" : "left" );
+ case PropertyId::LineHeight:
+ return mLineHeightPx > 0 ? String::fromFloat( mLineHeightPx, "px" ) : "normal";
+ case PropertyId::TextIndent:
+ return mTextIndentPx > 0 ? String::fromFloat( mTextIndentPx, "px" ) : "0";
default:
return UIHTMLWidget::getPropertyString( propertyDef, propertyIndex );
}
@@ -372,7 +403,7 @@ std::vector UIRichText::getPropertiesImplemented() const {
PropertyId::Color, PropertyId::TextShadowColor, PropertyId::TextShadowOffset,
PropertyId::TextStrokeWidth, PropertyId::TextStrokeColor, PropertyId::TextAlign,
PropertyId::SelectionColor, PropertyId::SelectionBackColor, PropertyId::TextSelection,
- PropertyId::TextDecoration };
+ PropertyId::TextDecoration, PropertyId::LineHeight, PropertyId::TextIndent };
props.insert( props.end(), local.begin(), local.end() );
return props;
}
@@ -542,6 +573,24 @@ UIRichText* UIRichText::setTextAlign( const Uint32& align ) {
return this;
}
+UIRichText* UIRichText::setLineHeightPx( Float height ) {
+ if ( mLineHeightPx != height ) {
+ mLineHeightPx = height;
+ notifyLayoutAttrChange();
+ notifyLayoutAttrChangeParent();
+ }
+ return this;
+}
+
+UIRichText* UIRichText::setTextIndentPx( Float indent ) {
+ if ( mTextIndentPx != indent ) {
+ mTextIndentPx = indent;
+ notifyLayoutAttrChange();
+ notifyLayoutAttrChangeParent();
+ }
+ return this;
+}
+
void UIRichText::loadFromXmlNode( const pugi::xml_node& node ) {
beginAttributesTransaction();
@@ -657,6 +706,11 @@ String UIRichText::UIRichText::collapseInternalWhitespace( const String& s ) {
void UIRichText::rebuildRichText( UILayout* container, RichText& richText, IntrinsicMode mode ) {
richText.clear();
+ if ( container->isType( UI_TYPE_RICHTEXT ) ) {
+ auto* uiRt = static_cast( container );
+ richText.setLineHeight( uiRt->getLineHeightPx() );
+ richText.setTextIndent( uiRt->getTextIndentPx() );
+ }
Float maxWidth = 0;
if ( container->getLayoutWidthPolicy() == SizePolicy::WrapContent ) {
maxWidth = container->getMatchParentWidth() - container->getPixelsContentOffset().Left -
diff --git a/src/examples/richtext/richtext.cpp b/src/examples/richtext/richtext.cpp
index ce3d00266..ccc0852c4 100644
--- a/src/examples/richtext/richtext.cpp
+++ b/src/examples/richtext/richtext.cpp
@@ -20,6 +20,8 @@ EE_MAIN_FUNC int main( int argc, char* argv[] ) {
richText.getFontStyleConfig().Font = font;
richText.getFontStyleConfig().CharacterSize = 24;
richText.setAlign( TEXT_ALIGN_LEFT );
+ richText.setLineHeight( 36 ); // Line height of 36px (1.5x 24px font)
+ richText.setTextIndent( 30 ); // Indent first line 30px
// Add spans using the helper method
richText.addSpan( "Hello " );