Fixes in whitespace collapsing, added white-space-collapse to the CSS specification.

This commit is contained in:
Martín Lucas Golini
2026-05-09 21:26:16 -03:00
parent 792bae6da2
commit 24bc5edd72
7 changed files with 88 additions and 3 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

View File

@@ -80,6 +80,7 @@ enum class PropertyId : Uint32 {
FontStyle = String::hash( "font-style" ),
TextDecoration = String::hash( "text-decoration" ),
Wordwrap = String::hash( "word-wrap" ),
WhiteSpaceCollapse = String::hash( "white-space-collapse" ),
TextStrokeWidth = String::hash( "text-stroke-width" ),
TextStrokeColor = String::hash( "text-stroke-color" ),
TextSelection = String::hash( "text-selection" ),

View File

@@ -11,6 +11,12 @@ class EE_API UIRichText : public UIHTMLWidget {
public:
enum class IntrinsicMode { None, Min, Max };
enum class WhiteSpaceCollapse { Collapse, Preserve, PreserveBreaks, PreserveSpaces, BreakSpaces };
static WhiteSpaceCollapse toWhiteSpaceCollapse( std::string val );
static std::string fromWhiteSpaceCollapse( WhiteSpaceCollapse val );
static String collapseInternalWhitespace( const String& s );
static void rebuildRichText( UILayout* container, RichText& richText,
@@ -113,6 +119,10 @@ class EE_API UIRichText : public UIHTMLWidget {
UIRichText* setTextAlign( const Uint32& align );
WhiteSpaceCollapse getWhiteSpaceCollapse() const;
void setWhiteSpaceCollapse( WhiteSpaceCollapse collapse );
Float getLineHeightPx() const;
UIRichText* setLineHeightEq( const std::string& eq );
@@ -152,6 +162,7 @@ class EE_API UIRichText : public UIHTMLWidget {
mutable bool mLineHeightPxDirty{ true };
mutable Float mTextIndentPxCache{ 0 };
mutable bool mTextIndentPxDirty{ true };
WhiteSpaceCollapse mWhiteSpaceCollapse{ WhiteSpaceCollapse::Collapse };
explicit UIRichText( const std::string& tag = "richtext" );

View File

@@ -335,6 +335,8 @@ void StyleSheetSpecification::registerDefaultProperties() {
registerProperty( "word-wrap", "" ).setType( PropertyType::Bool );
registerProperty( "white-space-collapse", "collapse", true ).setType( PropertyType::String );
registerProperty( "hint", "" ).setType( PropertyType::String );
registerProperty( "hint-color", "" ).setType( PropertyType::Color );
registerProperty( "hint-shadow-color", "" ).setType( PropertyType::Color );

View File

@@ -17,6 +17,35 @@
namespace EE { namespace UI {
UIRichText::WhiteSpaceCollapse UIRichText::toWhiteSpaceCollapse( std::string val ) {
String::toLowerInPlace( val );
if ( "preserve" == val )
return WhiteSpaceCollapse::Preserve;
if ( "preserve-breaks" == val || "preserve-breaks" == val )
return WhiteSpaceCollapse::PreserveBreaks;
if ( "preserve-spaces" == val || "preserve-spaces" == val )
return WhiteSpaceCollapse::PreserveSpaces;
if ( "break-spaces" == val || "break-spaces" == val )
return WhiteSpaceCollapse::BreakSpaces;
return WhiteSpaceCollapse::Collapse;
}
std::string UIRichText::fromWhiteSpaceCollapse( WhiteSpaceCollapse val ) {
switch ( val ) {
case WhiteSpaceCollapse::Preserve:
return "preserve";
case WhiteSpaceCollapse::PreserveBreaks:
return "preserve-breaks";
case WhiteSpaceCollapse::PreserveSpaces:
return "preserve-spaces";
case WhiteSpaceCollapse::BreakSpaces:
return "break-spaces";
case WhiteSpaceCollapse::Collapse:
default:
return "collapse";
}
}
UIHTMLHtml* UIHTMLHtml::New( const std::string& tag ) {
return eeNew( UIHTMLHtml, ( tag ) );
}
@@ -322,6 +351,9 @@ bool UIRichText::applyProperty( const StyleSheetProperty& attribute ) {
case PropertyId::TextIndent:
setTextIndentEq( attribute.value() );
break;
case PropertyId::WhiteSpaceCollapse:
setWhiteSpaceCollapse( toWhiteSpaceCollapse( attribute.value() ) );
break;
default:
return UIHTMLWidget::applyProperty( attribute );
}
@@ -370,6 +402,8 @@ std::string UIRichText::getPropertyString( const PropertyDefinition* propertyDef
return mLineHeightEq.empty() ? "normal" : mLineHeightEq;
case PropertyId::TextIndent:
return mTextIndentEq.empty() ? "0" : mTextIndentEq;
case PropertyId::WhiteSpaceCollapse:
return fromWhiteSpaceCollapse( mWhiteSpaceCollapse );
default:
return UIHTMLWidget::getPropertyString( propertyDef, propertyIndex );
}
@@ -382,7 +416,8 @@ 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::LineHeight, PropertyId::TextIndent };
PropertyId::TextDecoration, PropertyId::LineHeight, PropertyId::TextIndent,
PropertyId::WhiteSpaceCollapse };
props.insert( props.end(), local.begin(), local.end() );
return props;
}
@@ -553,6 +588,18 @@ UIRichText* UIRichText::setTextAlign( const Uint32& align ) {
return this;
}
UIRichText::WhiteSpaceCollapse UIRichText::getWhiteSpaceCollapse() const {
return mWhiteSpaceCollapse;
}
void UIRichText::setWhiteSpaceCollapse( WhiteSpaceCollapse collapse ) {
if ( mWhiteSpaceCollapse != collapse ) {
mWhiteSpaceCollapse = collapse;
notifyLayoutAttrChange();
notifyLayoutAttrChangeParent();
}
}
UIRichText* UIRichText::setLineHeightEq( const std::string& eq ) {
if ( mLineHeightEq != eq ) {
mLineHeightEq = eq;
@@ -734,6 +781,12 @@ void UIRichText::rebuildRichText( UILayout* container, RichText& richText, Intri
richText.setLineHeight( uiRt->getLineHeightPx() );
richText.setTextIndent( uiRt->getTextIndentPx() );
}
bool shouldCollapse =
container->isType( UI_TYPE_RICHTEXT )
? static_cast<UIRichText*>( container )->getWhiteSpaceCollapse() ==
WhiteSpaceCollapse::Collapse
: true;
bool lastSpanEndsWithSpace = false;
Float maxWidth = 0;
if ( container->getLayoutWidthPolicy() == SizePolicy::WrapContent ) {
maxWidth = container->getMatchParentWidth() - container->getPixelsContentOffset().Left -
@@ -769,7 +822,10 @@ void UIRichText::rebuildRichText( UILayout* container, RichText& richText, Intri
UITextSpan* selfSpan = container->asType<UITextSpan>();
if ( !selfSpan->getText().empty() && !selfSpan->isMergeable() &&
NULL != selfSpan->getFontStyleConfig().Font ) {
richText.addSpan( selfSpan->getText(), selfSpan->getFontStyleConfig() );
String::View selfText = selfSpan->getText().view();
richText.addSpan( selfText, selfSpan->getFontStyleConfig() );
if ( shouldCollapse )
lastSpanEndsWithSpace = !selfText.empty() && selfText.back() == ' ';
}
}
@@ -830,6 +886,9 @@ void UIRichText::rebuildRichText( UILayout* container, RichText& richText, Intri
if ( !nextIsInline && !text.empty() && text.back() == ' ' )
text = text.substr( 0, text.size() - 1 );
if ( shouldCollapse && lastSpanEndsWithSpace && !text.empty() && text[0] == ' ' )
text = text.substr( 1 );
if ( text.empty() ) {
textNode->setLayoutCharCount( 0 );
return;
@@ -837,6 +896,9 @@ void UIRichText::rebuildRichText( UILayout* container, RichText& richText, Intri
textNode->setLayoutCharCount( text.length() );
if ( shouldCollapse )
lastSpanEndsWithSpace = text.back() == ' ';
FontStyleConfig style;
if ( node->getParent()->isType( UI_TYPE_TEXTSPAN ) ) {
style = node->getParent()->asType<UITextSpan>()->getFontStyleConfig();
@@ -877,8 +939,15 @@ void UIRichText::rebuildRichText( UILayout* container, RichText& richText, Intri
if ( !nextIsInline && !spanText.empty() && spanText.back() == ' ' )
spanText = spanText.substr( 0, spanText.size() - 1 );
if ( !spanText.empty() )
if ( shouldCollapse && lastSpanEndsWithSpace && !spanText.empty() &&
spanText[0] == ' ' )
spanText = spanText.substr( 1 );
if ( !spanText.empty() ) {
richText.addSpan( spanText, span->getFontStyleConfig(), margin, padding );
if ( shouldCollapse )
lastSpanEndsWithSpace = spanText.back() == ' ';
}
} else if ( margin.Left > 0 || margin.Top > 0 || padding.Left > 0 || padding.Top > 0 ) {
Rectf leftOnly( margin.Left, margin.Top, 0, 0 );
Rectf padLeftOnly( padding.Left, padding.Top, 0, 0 );
@@ -903,6 +972,7 @@ void UIRichText::rebuildRichText( UILayout* container, RichText& richText, Intri
} else if ( widget->isType( UI_TYPE_BR ) ) {
richText.addSpan( "\n",
widget->asType<UILineBreak>()->getRichText().getFontStyleConfig() );
lastSpanEndsWithSpace = false;
} else {
Rectf margin = widget->getLayoutPixelsMargin();
bool isBlock = widget->getLayoutWidthPolicy() == SizePolicy::MatchParent;
@@ -960,6 +1030,7 @@ void UIRichText::rebuildRichText( UILayout* container, RichText& richText, Intri
richText.addCustomSize( Sizef( w + margin.Left + margin.Right,
size.getHeight() + margin.Top + margin.Bottom ),
isBlock, floatType, clearType );
lastSpanEndsWithSpace = false;
}
};