Fixes in how we process the CSS selector specificity.

Fixes in selector rule parser.
Stub implementation for attributes filter in CSS.
Fixes in `background-position` and `background` shorthand parsers.
Added a few HTML elements tags.
Added body default left and right margin.
This commit is contained in:
Martín Lucas Golini
2026-05-11 14:43:24 -03:00
parent d2d13cbe41
commit bd9e70fb83
12 changed files with 308 additions and 188 deletions
+4 -2
View File
@@ -77,12 +77,14 @@ del {
}
i,
em {
em,
cite {
font-style: italic;
}
CodeEditor,
code {
code,
kbd {
font-family: monospace;
}
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

+6 -6
View File
@@ -39,11 +39,11 @@ class EE_API StyleSheetProperty {
bool cachedProperty = false );
explicit StyleSheetProperty( const std::string& name, const std::string& value,
bool trimValue = true, const Uint32& specificity = 0,
bool trimValue = true, const Uint64& specificity = 0,
const Uint32& index = 0 );
explicit StyleSheetProperty( const std::string& name, const std::string& value,
const Uint32& specificity, bool isVolatile = false,
const Uint64& specificity, bool isVolatile = false,
const Uint32& index = 0 );
Uint32 getId() const;
@@ -56,9 +56,9 @@ class EE_API StyleSheetProperty {
const std::string& value() const;
const Uint32& getSpecificity() const;
const Uint64& getSpecificity() const;
void setSpecificity( const Uint32& specificity );
void setSpecificity( const Uint64& specificity );
bool isEmpty() const;
@@ -193,7 +193,7 @@ class EE_API StyleSheetProperty {
String::HashType mNameHash;
std::string mValue;
String::HashType mValueHash;
Uint32 mSpecificity;
Uint64 mSpecificity;
Uint32 mIndex;
bool mVolatile : 1 { false };
bool mImportant : 1 { false };
@@ -206,7 +206,7 @@ class EE_API StyleSheetProperty {
std::vector<VariableFunctionCache> mVarCache;
explicit StyleSheetProperty( bool isVolatile, const PropertyDefinition* definition,
const std::string& value, const Uint32& specificity = 0,
const std::string& value, const Uint64& specificity = 0,
const Uint32& index = 0 );
void cleanValue();
+65 -12
View File
@@ -41,18 +41,19 @@ class EE_API StyleSheetSelectorRule {
Id = 1 << 1,
Class = 1 << 2,
PseudoClass = 1 << 3,
StructuralPseudoClass = 1 << 4
StructuralPseudoClass = 1 << 4,
Attribute = 1 << 5,
};
enum SpecificityVal {
SpecificityImportant = UINT32_MAX,
SpecificityInline = UINT32_MAX - 1,
SpecificityId = 1000000,
SpecificityClass = 100000,
SpecificityTag = 10000,
SpecificityPseudoClass = 100,
SpecificityStructuralPseudoClass = 50,
SpecificityGlobal = 1
enum SpecificityVal : Uint64 {
SpecificityImportant = 1000000000000000ULL,
SpecificityInline = 1000000000000ULL,
SpecificityId = 1000000ULL,
SpecificityClass = 100000ULL,
SpecificityTag = 10000ULL,
SpecificityPseudoClass = 100ULL,
SpecificityStructuralPseudoClass = 50ULL,
SpecificityGlobal = 1ULL
};
enum PatternMatch {
@@ -64,6 +65,57 @@ class EE_API StyleSheetSelectorRule {
PREVIOUS_SIBLING = '|',
};
enum class AttributeOperator {
None,
Exact, // =
ContainsWord, // ~=
StartsWithDash, // |=
StartsWith, // ^=
EndsWith, // $=
Contains // *=
};
static AttributeOperator attributeOperatorFromString( const std::string& op ) {
if ( op == "=" )
return AttributeOperator::Exact;
if ( op == "~=" )
return AttributeOperator::ContainsWord;
if ( op == "|=" )
return AttributeOperator::StartsWithDash;
if ( op == "^=" )
return AttributeOperator::StartsWith;
if ( op == "$=" )
return AttributeOperator::EndsWith;
if ( op == "*=" )
return AttributeOperator::Contains;
return AttributeOperator::None;
}
static std::string attributeOperatorToString( AttributeOperator op ) {
switch ( op ) {
case AttributeOperator::Exact:
return "=";
case AttributeOperator::ContainsWord:
return "~=";
case AttributeOperator::StartsWithDash:
return "|=";
case AttributeOperator::StartsWith:
return "^=";
case AttributeOperator::EndsWith:
return "$=";
case AttributeOperator::Contains:
return "*=";
default:
return "";
}
}
struct AttributeSelector {
std::string name;
AttributeOperator op{ AttributeOperator::None };
std::string value;
};
static PseudoClasses toPseudoClass( std::string_view cls );
static std::vector<const char*> fromPseudoClass( Uint32 cls );
@@ -76,7 +128,7 @@ class EE_API StyleSheetSelectorRule {
const PatternMatch& getPatternMatch() const { return mPatternMatch; }
const int& getSpecificity() const { return mSpecificity; }
const Uint64& getSpecificity() const { return mSpecificity; }
bool matches( UIWidget* element, const bool& applyPseudo = true ) const;
@@ -99,13 +151,14 @@ class EE_API StyleSheetSelectorRule {
const std::string& getId() const;
protected:
int mSpecificity{ 0 };
Uint64 mSpecificity{ 0 };
PatternMatch mPatternMatch;
std::string mTagName;
std::string mId;
std::vector<std::string> mClasses;
std::vector<std::string> mStructuralPseudoClasses;
std::vector<StructuralSelector> mStructuralSelectors;
std::vector<AttributeSelector> mAttributeSelectors;
Uint32 mPseudoClasses{ 0 };
Uint32 mRequirementFlags{ 0 };
};
+12 -12
View File
@@ -260,7 +260,7 @@ std::shared_ptr<ElementDefinition> StyleSheet::getElementStyles( UIWidget* eleme
}
}
std::sort( applicableNodes.begin(), applicableNodes.end(), StyleSheetNodeSort );
std::stable_sort( applicableNodes.begin(), applicableNodes.end(), StyleSheetNodeSort );
if ( applicableNodes.empty() )
return nullptr;
@@ -304,17 +304,17 @@ StyleSheet::getStyleFromSelector( const std::string& selector, bool searchBySpec
if ( node->isMediaValid() && node->getSelector().getName() == selector )
found.push_back( node );
if ( !found.empty() ) {
std::sort( found.begin(), found.end(),
[]( const std::shared_ptr<StyleSheetStyle>& lhs,
const std::shared_ptr<StyleSheetStyle>& rhs ) {
if ( ( lhs->getMediaQueryList() == nullptr ) !=
( rhs->getMediaQueryList() == nullptr ) ) {
return ( lhs->getMediaQueryList() == nullptr ) >
( rhs->getMediaQueryList() == nullptr );
}
return lhs->getSelector().getSpecificity() <
rhs->getSelector().getSpecificity();
} );
std::stable_sort( found.begin(), found.end(),
[]( const std::shared_ptr<StyleSheetStyle>& lhs,
const std::shared_ptr<StyleSheetStyle>& rhs ) {
if ( ( lhs->getMediaQueryList() == nullptr ) !=
( rhs->getMediaQueryList() == nullptr ) ) {
return ( lhs->getMediaQueryList() == nullptr ) >
( rhs->getMediaQueryList() == nullptr );
}
return lhs->getSelector().getSpecificity() <
rhs->getSelector().getSpecificity();
} );
return found.back();
}
}
+16 -12
View File
@@ -44,7 +44,7 @@ StyleSheetProperty::StyleSheetProperty( const PropertyDefinition* definition,
}
StyleSheetProperty::StyleSheetProperty( bool isVolatile, const PropertyDefinition* definition,
const std::string& value, const Uint32& /*specificity*/,
const std::string& value, const Uint64& /*specificity*/,
const Uint32& index ) :
mName( definition->getName() ),
mNameHash( definition->getId() ),
@@ -68,7 +68,7 @@ StyleSheetProperty::StyleSheetProperty( bool isVolatile, const PropertyDefinitio
}
StyleSheetProperty::StyleSheetProperty( const std::string& name, const std::string& value,
bool trimValue, const Uint32& specificity,
bool trimValue, const Uint64& specificity,
const Uint32& index ) :
mName( String::toLower( String::trim( name ) ) ),
mNameHash( String::hash( mName ) ),
@@ -95,7 +95,7 @@ StyleSheetProperty::StyleSheetProperty( const std::string& name, const std::stri
}
StyleSheetProperty::StyleSheetProperty( const std::string& name, const std::string& value,
const Uint32& specificity, bool isVolatile,
const Uint64& specificity, bool isVolatile,
const Uint32& index ) :
mName( String::toLower( String::trim( name ) ) ),
mNameHash( String::hash( mName ) ),
@@ -138,16 +138,14 @@ const std::string& StyleSheetProperty::value() const {
return mValue;
}
const Uint32& StyleSheetProperty::getSpecificity() const {
const Uint64& StyleSheetProperty::getSpecificity() const {
return mSpecificity;
}
void StyleSheetProperty::setSpecificity( const Uint32& specificity ) {
// Don't allow set specificity if is an important property,
// force the specificity in this case.
if ( !mImportant ) {
mSpecificity = specificity;
}
void StyleSheetProperty::setSpecificity( const Uint64& specificity ) {
mSpecificity = specificity;
if ( mImportant )
mSpecificity += StyleSheetSelectorRule::SpecificityImportant;
}
bool StyleSheetProperty::isEmpty() const {
@@ -190,7 +188,7 @@ const String::HashType& StyleSheetProperty::getNameHash() const {
void StyleSheetProperty::checkImportant() {
if ( String::endsWith( mValue, "!important" ) ) {
mImportant = true;
mSpecificity = StyleSheetSelectorRule::SpecificityImportant;
mSpecificity += StyleSheetSelectorRule::SpecificityImportant;
mValue = String::trim( mValue.substr( 0, mValue.size() - 10 /*!important*/ ) );
mValueHash = String::hash( mValue );
}
@@ -689,8 +687,14 @@ bool StyleSheetProperty::isCachedProperty() const {
}
void StyleSheetProperty::setImportant( bool important ) {
if ( mImportant == important )
return;
mImportant = important;
mSpecificity = StyleSheetSelectorRule::SpecificityImportant;
if ( important ) {
mSpecificity += StyleSheetSelectorRule::SpecificityImportant;
} else {
mSpecificity -= StyleSheetSelectorRule::SpecificityImportant;
}
}
}}} // namespace EE::UI::CSS
+138 -107
View File
@@ -63,54 +63,6 @@ StyleSheetSelectorRule::toPseudoClass( std::string_view cls ) {
return StyleSheetSelectorRule::PseudoClasses::None;
}
static void splitSelectorPseudoClass( const std::string& selector, std::string& realSelector,
std::string& realPseudoClass ) {
if ( !selector.empty() ) {
bool lastWasColon = false;
bool inFunction = false;
for ( int i = (Int32)selector.size() - 1; i >= 0; i-- ) {
char curChar = selector[i];
if ( inFunction && curChar == '(' )
inFunction = false;
if ( inFunction )
continue;
if ( lastWasColon ) {
if ( StyleSheetSelectorRule::PSEUDO_CLASS == curChar ) {
// no pseudo class
realSelector = selector;
} else {
if ( i + 2 <= (int)selector.size() ) {
realSelector = selector.substr( 0, i + 1 );
realPseudoClass = selector.substr( i + 2 );
} else {
realSelector = selector;
}
}
return;
} else if ( StyleSheetSelectorRule::PSEUDO_CLASS == curChar ) {
lastWasColon = true;
}
if ( curChar == ')' ) {
inFunction = true;
lastWasColon = false;
}
}
if ( lastWasColon ) {
if ( selector.size() > 1 )
realPseudoClass = selector.substr( 1 );
} else {
realSelector = selector;
}
}
}
std::vector<const char*> StyleSheetSelectorRule::fromPseudoClass( Uint32 cls ) {
std::vector<const char*> ret;
ret.reserve( numberOfSetBits( cls ) );
@@ -151,77 +103,111 @@ void StyleSheetSelectorRule::pushSelectorTypeIdentifier( TypeIdentifier selector
}
void StyleSheetSelectorRule::parseFragment( const std::string& selectorFragment ) {
std::string selector = selectorFragment;
std::string realSelector = "";
std::string pseudoClass = "";
do {
pseudoClass.clear();
realSelector.clear();
if ( !selectorFragment.empty() && selectorFragment[0] != ':' ) {
splitSelectorPseudoClass( selector, realSelector, pseudoClass );
if ( !pseudoClass.empty() ) {
if ( isPseudoClassState( pseudoClass ) ) {
mPseudoClasses |= toPseudoClass( pseudoClass );
} else if ( isStructuralPseudoClass( pseudoClass ) ) {
mStructuralPseudoClasses.push_back( pseudoClass );
StructuralSelector structuralSelector =
StyleSheetSpecification::instance()->getStructuralSelector( pseudoClass );
if ( structuralSelector.selector ) {
mStructuralSelectors.push_back( structuralSelector );
}
}
selector = realSelector;
}
}
} while ( !pseudoClass.empty() );
TypeIdentifier curSelectorType = TAG;
std::string buffer;
int functionDepth = 0;
bool inAttribute = false;
for ( auto charIt = selector.begin(); charIt != selector.end(); ++charIt ) {
char curChar = *charIt;
auto processBuffer = [&]() {
if ( buffer.empty() )
return;
switch ( curChar ) {
case CLASS: {
if ( !buffer.empty() ) {
pushSelectorTypeIdentifier( curSelectorType, buffer );
buffer.clear();
if ( inAttribute ) {
AttributeSelector attr;
size_t opPos = buffer.find_first_of( "=~|^$*" );
if ( opPos != std::string::npos ) {
size_t eqPos = buffer.find( '=', opPos );
attr.name = String::trim( buffer.substr( 0, opPos ) );
// Extract the string operator and convert to Enum
std::string opStr = buffer.substr( opPos, ( eqPos - opPos ) + 1 );
attr.op = attributeOperatorFromString( opStr );
std::string val = String::trim( buffer.substr( eqPos + 1 ) );
String::trimInPlace( val, '"' );
String::trimInPlace( val, '\'' );
attr.value = val;
} else {
attr.name = String::trim( buffer );
attr.op = AttributeOperator::None;
}
mAttributeSelectors.push_back( attr );
mSpecificity += SpecificityClass;
}
if ( curSelectorType == TAG || curSelectorType == CLASS || curSelectorType == ID ) {
if ( buffer.size() == 1 && buffer[0] == GLOBAL )
curSelectorType = GLOBAL;
pushSelectorTypeIdentifier( curSelectorType, buffer );
} else if ( curSelectorType == PSEUDO_CLASS ||
curSelectorType == STRUCTURAL_PSEUDO_CLASS ) {
if ( isPseudoClassState( buffer ) ) {
mPseudoClasses |= toPseudoClass( buffer );
} else if ( isStructuralPseudoClass( buffer ) ) {
mStructuralPseudoClasses.push_back( buffer );
StructuralSelector structuralSelector =
StyleSheetSpecification::instance()->getStructuralSelector( buffer );
if ( structuralSelector.selector ) {
mStructuralSelectors.push_back( structuralSelector );
}
curSelectorType = CLASS;
break;
} else if ( buffer == "root" ) {
// :root defines global variables. Map it to the GLOBAL wildcard
// so variables are correctly attached to the root node hierarchy.
pushSelectorTypeIdentifier( GLOBAL, "*" );
} else {
// Poison the rule for genuinely unsupported pseudo-classes
pushSelectorTypeIdentifier( TAG, ":" + buffer );
}
case ID: {
if ( !buffer.empty() ) {
pushSelectorTypeIdentifier( curSelectorType, buffer );
buffer.clear();
}
}
buffer.clear();
};
curSelectorType = ID;
// Single forward pass
for ( size_t i = 0; i < selectorFragment.size(); i++ ) {
char curChar = selectorFragment[i];
break;
}
default: {
buffer += curChar;
break;
}
if ( curChar == '(' )
functionDepth++;
else if ( curChar == ')' )
functionDepth--;
// Catch attribute brackets
if ( curChar == '[' && functionDepth == 0 ) {
processBuffer(); // Flush previous tag/class
inAttribute = true;
continue;
} else if ( curChar == ']' && inAttribute ) {
processBuffer(); // Parse the attribute we just accumulated
inAttribute = false;
continue;
}
// Protect `eepp` sub-widgets. If we see `::`, it is part of the
// tag name (e.g., tableview::cell). Append it and skip the split.
if ( curChar == ':' && i + 1 < selectorFragment.size() && selectorFragment[i + 1] == ':' ) {
buffer += "::";
i++; // Skip the second colon
continue;
}
// Only trigger a split if we aren't inside a function like :not()
if ( functionDepth == 0 &&
( curChar == CLASS || curChar == ID || curChar == PSEUDO_CLASS ) ) {
processBuffer();
curSelectorType = (TypeIdentifier)curChar;
} else {
buffer += curChar;
}
}
if ( !buffer.empty() ) {
if ( buffer.size() == 1 && buffer[0] == GLOBAL )
curSelectorType = GLOBAL;
pushSelectorTypeIdentifier( curSelectorType, buffer );
}
processBuffer();
// Calculate Requirement Flags and Specificity
if ( !mTagName.empty() )
mRequirementFlags |= TagName;
@@ -322,6 +308,51 @@ bool StyleSheetSelectorRule::matches( UIWidget* element, const bool& applyPseudo
}
}
if ( !mAttributeSelectors.empty() ) {
for ( const auto& attr : mAttributeSelectors ) {
if ( attr.op != AttributeOperator::None ) {
std::string elVal = element->getPropertyString( attr.name );
switch ( attr.op ) {
case AttributeOperator::Exact: // =
if ( elVal != attr.value )
return false;
break;
case AttributeOperator::StartsWith: // ^=
if ( !String::startsWith( elVal, attr.value ) )
return false;
break;
case AttributeOperator::EndsWith: // $=
if ( !String::endsWith( elVal, attr.value ) )
return false;
break;
case AttributeOperator::Contains: // *=
if ( elVal.find( attr.value ) == std::string::npos )
return false;
break;
case AttributeOperator::ContainsWord: { // ~= (Space-separated word check)
auto words = String::split( elVal, ' ', true );
if ( std::find( words.begin(), words.end(), attr.value ) == words.end() ) {
return false;
}
break;
}
case AttributeOperator::StartsWithDash: // |= (Exact match or starts with value
// + "-")
if ( elVal != attr.value &&
!String::startsWith( elVal, attr.value + "-" ) ) {
return false;
}
break;
default:
break;
}
}
}
flags |= Attribute;
}
if ( applyPseudo ) {
if ( mPseudoClasses && element->getStyleSheetPseudoClasses() ) {
bool hasPseudoClasses = true;
+19 -6
View File
@@ -916,6 +916,17 @@ void StyleSheetSpecification::registerDefaultShorthandParsers() {
if ( value.empty() )
return {};
bool isImportant = false;
if ( String::icontains( value, "!important" ) ) {
std::string lowerVal = String::toLower( value );
size_t impPos = lowerVal.rfind( "!important" );
if ( impPos != std::string::npos ) {
isImportant = true;
value.erase( impPos );
value = String::trim( value );
}
}
const std::vector<std::string>& propNames = shorthand->getProperties();
std::vector<std::string> values = String::split( value, ',' );
@@ -1007,10 +1018,12 @@ void StyleSheetSpecification::registerDefaultShorthandParsers() {
}
std::vector<StyleSheetProperty> properties;
std::string impStr = isImportant ? " !important" : "";
if ( !propNames.empty() )
properties.emplace_back( propNames[0], String::join( xValues, ',' ) );
properties.emplace_back( propNames[0], String::join( xValues, ',' ) + impStr );
if ( propNames.size() > 1 )
properties.emplace_back( propNames[1], String::join( yValues, ',' ) );
properties.emplace_back( propNames[1], String::join( yValues, ',' ) + impStr );
return properties;
};
@@ -1022,7 +1035,7 @@ void StyleSheetSpecification::registerDefaultShorthandParsers() {
if ( value.empty() || "none" == value )
return {};
// 1. Extract !important early so it doesn't get captured as a color token
// Extract !important early so it doesn't get captured as a color token
bool isImportant = false;
if ( String::icontains( value, "!important" ) ) {
isImportant = true;
@@ -1030,7 +1043,7 @@ void StyleSheetSpecification::registerDefaultShorthandParsers() {
String::replaceAll( value, "! important", "" );
}
// 2. Ensure functional notations (like url) are separated by a space
// Ensure functional notations (like url) are separated by a space
// so that standard token splitting works correctly for minified CSS.
String::replaceAll( value, ")", ") " );
String::removeExtraSpaces( value );
@@ -1055,7 +1068,7 @@ void StyleSheetSpecification::registerDefaultShorthandParsers() {
return s == "left" || s == "right" || s == "top" || s == "bottom" || s == "center";
};
// 3. Split by comma for multi-layer support, while strictly ignoring
// Split by comma for multi-layer support, while strictly ignoring
// commas inside parentheses (required for data: URIs and functions)
std::vector<std::string> layers = String::split( value, ",", "", "(" );
@@ -1128,7 +1141,7 @@ void StyleSheetSpecification::registerDefaultShorthandParsers() {
}
}
// 4. Re-apply the !important flag to the mapped individual longhands
// Re-apply the !important flag to the mapped individual longhands
std::string impStr = isImportant ? " !important" : "";
for ( auto& propName : propNames ) {
+45 -31
View File
@@ -17,6 +17,38 @@
namespace EE { namespace UI {
static void applyDefaultBlockMargins( UIWidget* widget, const std::string& tag ) {
static const UnorderedMap<std::string, std::pair<Float, Float>> defaultsTopBottom = {
{ "h1", { 0.67f, 0.67f } }, { "h2", { 0.83f, 0.83f } },
{ "h3", { 1.00f, 1.00f } }, { "h4", { 1.33f, 1.33f } },
{ "h5", { 1.67f, 1.67f } }, { "h6", { 2.33f, 2.33f } },
{ "p", { 1.00f, 1.00f } }, { "pre", { 1.00f, 1.00f } },
{ "blockquote", { 1.00f, 1.00f } }, { "hr", { 0.50f, 0.50f } },
{ "ul", { 1.00f, 1.00f } }, { "ol", { 1.00f, 1.00f } },
{ "dl", { 1.00f, 1.00f } }, { "body", { 0.67f, 0.67f } },
};
static const UnorderedMap<std::string, std::pair<Float, Float>> defaultsLeftRight = {
{ "body", { 0.67f, 0.67f } },
};
auto ittb = defaultsTopBottom.find( tag );
if ( ittb != defaultsTopBottom.end() ) {
widget->applyProperty(
StyleSheetProperty( "margin-top", String::format( "%gem", ittb->second.first ) ) );
widget->applyProperty(
StyleSheetProperty( "margin-bottom", String::format( "%gem", ittb->second.second ) ) );
}
auto itlr = defaultsLeftRight.find( tag );
if ( itlr != defaultsLeftRight.end() ) {
widget->applyProperty(
StyleSheetProperty( "margin-left", String::format( "%gem", itlr->second.first ) ) );
widget->applyProperty(
StyleSheetProperty( "margin-right", String::format( "%gem", itlr->second.second ) ) );
}
}
UIRichText::WhiteSpaceCollapse UIRichText::toWhiteSpaceCollapse( std::string val ) {
String::toLowerInPlace( val );
if ( "preserve" == val )
@@ -179,6 +211,7 @@ UIRichText* UIRichText::NewHtml() {
UIRichText* UIRichText::NewBody() {
auto* body = UIHTMLBody::New( "body" );
body->setClipType( ClipType::None );
applyDefaultBlockMargins( body, body->getElementTag() );
return body;
}
@@ -186,25 +219,6 @@ UIRichText* UIRichText::NewBr() {
return UILineBreak::New( "br" );
};
static void applyDefaultBlockMargins( UIWidget* widget, const std::string& tag ) {
static const UnorderedMap<std::string, std::pair<Float, Float>> defaults = {
{ "h1", { 0.67f, 0.67f } }, { "h2", { 0.83f, 0.83f } },
{ "h3", { 1.00f, 1.00f } }, { "h4", { 1.33f, 1.33f } },
{ "h5", { 1.67f, 1.67f } }, { "h6", { 2.33f, 2.33f } },
{ "p", { 1.00f, 1.00f } }, { "pre", { 1.00f, 1.00f } },
{ "blockquote", { 1.00f, 1.00f } }, { "hr", { 0.50f, 0.50f } },
{ "ul", { 1.00f, 1.00f } }, { "ol", { 1.00f, 1.00f } },
{ "dl", { 1.00f, 1.00f } }, { "body", { 0.67f, 0.67f } },
};
auto it = defaults.find( tag );
if ( it != defaults.end() ) {
widget->applyProperty(
StyleSheetProperty( "margin-top", String::format( "%gem", it->second.first ) ) );
widget->applyProperty(
StyleSheetProperty( "margin-bottom", String::format( "%gem", it->second.second ) ) );
}
}
UIRichText* UIRichText::NewHr() {
auto* w = UILineBreak::New( "hr" );
applyDefaultBlockMargins( w, "hr" );
@@ -411,13 +425,14 @@ std::string UIRichText::getPropertyString( const PropertyDefinition* propertyDef
std::vector<PropertyId> UIRichText::getPropertiesImplemented() const {
auto props = UIHTMLWidget::getPropertiesImplemented();
auto local = {
PropertyId::FontFamily, PropertyId::FontSize, PropertyId::FontStyle,
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::WhiteSpaceCollapse };
auto local = { PropertyId::FontFamily, PropertyId::FontSize,
PropertyId::FontStyle, 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::WhiteSpaceCollapse };
props.insert( props.end(), local.begin(), local.end() );
return props;
}
@@ -781,11 +796,10 @@ 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 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 ) {
+3
View File
@@ -143,6 +143,8 @@ void UIWidgetCreator::createBaseWidgetList() {
registeredWidget["strong"] = UITextSpan::NewStrong;
registeredWidget["small"] = UITextSpan::NewSmall;
registeredWidget["i"] = UITextSpan::NewItalics;
registeredWidget["cite"] = [] { return UITextSpan::NewWithTag( "cite" ); };
registeredWidget["kbd"] = [] { return UITextSpan::NewWithTag( "kbd" ); };
registeredWidget["u"] = UITextSpan::NewUnderline;
registeredWidget["ins"] = UITextSpan::NewUnderline;
registeredWidget["s"] = UITextSpan::NewStrikethrough;
@@ -203,6 +205,7 @@ void UIWidgetCreator::createBaseWidgetList() {
registeredWidget["section"] = [] { return UIRichText::NewWithTag( "section" ); };
registeredWidget["nav"] = [] { return UIRichText::NewWithTag( "nav" ); };
registeredWidget["center"] = [] { return UIRichText::NewWithTag( "center" ); };
registeredWidget["aside"] = [] { return UIRichText::NewWithTag( "aside" ); };
registeredWidget["html"] = UIRichText::NewHtml;
registeredWidget["head"] = [] { return UIWidget::NewWithTag( "head" ); };
registeredWidget["body"] = UIRichText::NewBody;