mirror of
https://github.com/SpartanJ/eepp.git
synced 2026-10-27 16:38:20 +03:00
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:
@@ -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 |
@@ -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();
|
||||
|
||||
@@ -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 };
|
||||
};
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
@@ -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 ) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user