Add line-height and text-indent support.

This commit is contained in:
Martín Lucas Golini
2026-05-09 01:38:43 -03:00
parent 0c3df503b4
commit bc071bd88d
9 changed files with 108 additions and 5 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.2 KiB

After

Width:  |  Height:  |  Size: 6.2 KiB

View File

@@ -155,6 +155,19 @@ class EE_API RichText : public Drawable {
/** @return The list of rendered lines. */
const std::vector<RenderParagraph>& 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

View File

@@ -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" ),

View File

@@ -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" );

View File

@@ -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;

View File

@@ -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" );

View File

@@ -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<PropertyId> 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<UIRichText*>( container );
richText.setLineHeight( uiRt->getLineHeightPx() );
richText.setTextIndent( uiRt->getTextIndentPx() );
}
Float maxWidth = 0;
if ( container->getLayoutWidthPolicy() == SizePolicy::WrapContent ) {
maxWidth = container->getMatchParentWidth() - container->getPixelsContentOffset().Left -

View File

@@ -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 " );