diff --git a/bin/assets/ui/breeze.css b/bin/assets/ui/breeze.css index bd5268976..b5040944a 100644 --- a/bin/assets/ui/breeze.css +++ b/bin/assets/ui/breeze.css @@ -77,12 +77,14 @@ del { } i, -em { +em, +cite { font-style: italic; } CodeEditor, -code { +code, +kbd { font-family: monospace; } diff --git a/bin/unit_tests/assets/html/eepp-uihtmltable-complex-layout-2.webp b/bin/unit_tests/assets/html/eepp-uihtmltable-complex-layout-2.webp index f6eb34480..8c4ac0df4 100644 Binary files a/bin/unit_tests/assets/html/eepp-uihtmltable-complex-layout-2.webp and b/bin/unit_tests/assets/html/eepp-uihtmltable-complex-layout-2.webp differ diff --git a/bin/unit_tests/assets/html/eepp-uihtmltable-complex-layout-3.webp b/bin/unit_tests/assets/html/eepp-uihtmltable-complex-layout-3.webp index d10d21890..131d62aa2 100644 Binary files a/bin/unit_tests/assets/html/eepp-uihtmltable-complex-layout-3.webp and b/bin/unit_tests/assets/html/eepp-uihtmltable-complex-layout-3.webp differ diff --git a/bin/unit_tests/assets/html/eepp-uihtmltable-complex-layout.webp b/bin/unit_tests/assets/html/eepp-uihtmltable-complex-layout.webp index 0d96cc2b7..cf3e3084d 100644 Binary files a/bin/unit_tests/assets/html/eepp-uihtmltable-complex-layout.webp and b/bin/unit_tests/assets/html/eepp-uihtmltable-complex-layout.webp differ diff --git a/include/eepp/ui/css/stylesheetproperty.hpp b/include/eepp/ui/css/stylesheetproperty.hpp index 2567404d0..6874ad0d0 100644 --- a/include/eepp/ui/css/stylesheetproperty.hpp +++ b/include/eepp/ui/css/stylesheetproperty.hpp @@ -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 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(); diff --git a/include/eepp/ui/css/stylesheetselectorrule.hpp b/include/eepp/ui/css/stylesheetselectorrule.hpp index 684fcb882..f549f763f 100644 --- a/include/eepp/ui/css/stylesheetselectorrule.hpp +++ b/include/eepp/ui/css/stylesheetselectorrule.hpp @@ -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 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 mClasses; std::vector mStructuralPseudoClasses; std::vector mStructuralSelectors; + std::vector mAttributeSelectors; Uint32 mPseudoClasses{ 0 }; Uint32 mRequirementFlags{ 0 }; }; diff --git a/src/eepp/ui/css/stylesheet.cpp b/src/eepp/ui/css/stylesheet.cpp index 2fcf19bb5..a42b44cc8 100644 --- a/src/eepp/ui/css/stylesheet.cpp +++ b/src/eepp/ui/css/stylesheet.cpp @@ -260,7 +260,7 @@ std::shared_ptr 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& lhs, - const std::shared_ptr& 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& lhs, + const std::shared_ptr& 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(); } } diff --git a/src/eepp/ui/css/stylesheetproperty.cpp b/src/eepp/ui/css/stylesheetproperty.cpp index fe558eacf..7d5881fdf 100644 --- a/src/eepp/ui/css/stylesheetproperty.cpp +++ b/src/eepp/ui/css/stylesheetproperty.cpp @@ -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 diff --git a/src/eepp/ui/css/stylesheetselectorrule.cpp b/src/eepp/ui/css/stylesheetselectorrule.cpp index 8e0872b73..3cab395de 100644 --- a/src/eepp/ui/css/stylesheetselectorrule.cpp +++ b/src/eepp/ui/css/stylesheetselectorrule.cpp @@ -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 StyleSheetSelectorRule::fromPseudoClass( Uint32 cls ) { std::vector 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; diff --git a/src/eepp/ui/css/stylesheetspecification.cpp b/src/eepp/ui/css/stylesheetspecification.cpp index ad424d895..8497f5dcc 100644 --- a/src/eepp/ui/css/stylesheetspecification.cpp +++ b/src/eepp/ui/css/stylesheetspecification.cpp @@ -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& propNames = shorthand->getProperties(); std::vector values = String::split( value, ',' ); @@ -1007,10 +1018,12 @@ void StyleSheetSpecification::registerDefaultShorthandParsers() { } std::vector 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 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 ) { diff --git a/src/eepp/ui/uirichtext.cpp b/src/eepp/ui/uirichtext.cpp index 9001d3c32..27e5ad0a0 100644 --- a/src/eepp/ui/uirichtext.cpp +++ b/src/eepp/ui/uirichtext.cpp @@ -17,6 +17,38 @@ namespace EE { namespace UI { +static void applyDefaultBlockMargins( UIWidget* widget, const std::string& tag ) { + static const UnorderedMap> 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> 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> 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 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( container )->getWhiteSpaceCollapse() == - WhiteSpaceCollapse::Collapse - : true; + bool shouldCollapse = container->isType( UI_TYPE_RICHTEXT ) + ? static_cast( container )->getWhiteSpaceCollapse() == + WhiteSpaceCollapse::Collapse + : true; bool lastSpanEndsWithSpace = false; Float maxWidth = 0; if ( container->getLayoutWidthPolicy() == SizePolicy::WrapContent ) { diff --git a/src/eepp/ui/uiwidgetcreator.cpp b/src/eepp/ui/uiwidgetcreator.cpp index 4e0970fec..5722c0a5e 100644 --- a/src/eepp/ui/uiwidgetcreator.cpp +++ b/src/eepp/ui/uiwidgetcreator.cpp @@ -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;