Fix hex color string parsing.

UIMarkdownView now uses the HTMLFormatter::HTMLtoXML.
Removed formatting hack to force strict XML from HTML, now we should always use HTMLFormatter::HTMLtoXML.
This commit is contained in:
Martín Lucas Golini
2026-05-20 01:39:05 -03:00
parent 7daca81bd6
commit 0defa0ada3
7 changed files with 82 additions and 103 deletions

View File

@@ -377,7 +377,7 @@
"working_dir": "${project_root}/bin"
},
{
"args": "-d1 file:///home/downloads/files/svn/eepp/bin/unit_tests/assets/html/blog_main_incorrect_widths.html",
"args": "-c system --hn-dark",
"command": "${project_root}/bin/eepp-ui-html-debug",
"name": "eepp-ui-html-debug",
"working_dir": "${project_root}/bin"

View File

@@ -9,8 +9,6 @@
* Implement TableView and TreeView properties.
* Add automatic font-fallback for lang scripts
* Implement support for setting and creating a model from the XML.
* Implement support for very simple state-changes from the XML file (ex: onclick="toggleclass(x)").

View File

@@ -13,7 +13,7 @@ namespace EE { namespace System {
namespace {
template <typename T> inline T _round( T r ) {
return ( r > 0.0f ) ? eefloor( r + 0.5f ) : eeceil( r - 0.5f );
return ( r > 0.0f ) ? std::floor( r + 0.5f ) : std::ceil( r - 0.5f );
}
} // namespace
@@ -504,21 +504,29 @@ Color Color::fromString( std::string str ) {
return Color::Transparent;
if ( str[0] == '#' ) {
str = str.substr( 1 );
str.erase( 0, 1 );
size = str.size();
if ( 0 == size )
if ( size != 3 && size != 4 && size != 6 && size != 8 )
return Color::Transparent;
if ( size < 6 ) {
for ( std::size_t i = size; i < 6; i++ )
str += str[size - 1];
// Expand shorthand CSS notation
if ( size == 3 || size == 4 ) {
std::string expanded;
expanded.reserve( size * 2 );
size = 6;
for ( char c : str ) {
expanded += c;
expanded += c;
}
if ( 6 == size )
str = expanded;
size = str.size();
}
// Add opaque alpha if omitted
if ( size == 6 )
str += "FF";
return Color( std::strtoul( str.c_str(), NULL, 16 ) );
@@ -715,8 +723,7 @@ Color Color::fromString( std::string str ) {
return Color::Transparent;
}
template <typename StringType>
bool Color::isColorStringT( StringType str, bool searchColorNames ) {
template <typename StringType> bool Color::isColorStringT( StringType str, bool searchColorNames ) {
if ( str.empty() )
return false;
@@ -831,12 +838,18 @@ bool Color::validHexColorString( String::View hexColor ) {
}
bool Color::validHexColorString( std::string_view hexColor ) {
if ( hexColor.size() < 2 || hexColor[0] != '#' )
if ( hexColor.empty() || hexColor[0] != '#' )
return false;
const size_t len = hexColor.size() - 1;
if ( len != 3 && len != 4 && len != 6 && len != 8 )
return false;
for ( size_t i = 1; i < hexColor.size(); i++ ) {
if ( !( String::isNumber( hexColor[i] ) || ( hexColor[i] >= 'a' && hexColor[i] <= 'f' ) ||
( hexColor[i] >= 'A' && hexColor[i] <= 'F' ) ) ) {
char c = hexColor[i];
if ( !( String::isNumber( c ) || ( c >= 'a' && c <= 'f' ) || ( c >= 'A' && c <= 'F' ) ) ) {
return false;
}
}

View File

@@ -1,4 +1,5 @@
#include <eepp/ui/doc/markdownhelper.hpp>
#include <eepp/ui/tools/htmlformatter.hpp>
#include <eepp/ui/uimarkdownview.hpp>
#include <eepp/ui/uiscenenode.hpp>
@@ -28,8 +29,7 @@ bool UIMarkdownView::isType( const Uint32& type ) const {
void UIMarkdownView::loadFromString( std::string_view markdown ) {
closeAllChildren();
auto xhtml = Markdown::toXHTML( markdown );
// printf( "%s", xhtml.c_str() );
auto xhtml = Tools::HTMLFormatter::HTMLtoXML( Markdown::toXHTML( markdown ) );
getUISceneNode()->loadLayoutFromString( xhtml, this );
}

View File

@@ -679,6 +679,14 @@ Float UIRichText::getLineHeightPx() const {
mLineHeightPxCache = 0;
return 0;
}
// Temporal hack until we support calc
if ( mLineHeightEq.find( "calc(" ) != std::string::npos ||
mLineHeightEq.find( "var(" ) != std::string::npos ) {
mLineHeightPxCache = 0;
return 0;
}
bool isUnitless = !mLineHeightEq.empty();
for ( char c : mLineHeightEq ) {
if ( c != '-' && c != '+' && c != '.' && !String::isNumber( c, false ) ) {

View File

@@ -1,4 +1,3 @@
#include "eepp/ui/uirichtext.hpp"
#include <algorithm>
#include <eepp/core/string.hpp>
#include <eepp/graphics/fontmanager.hpp>
@@ -36,9 +35,6 @@ using namespace EE::Network;
namespace EE { namespace UI {
static constexpr std::string_view VOIDTAG_REGEX =
"(<(?:img|br|hr|input|meta|link)\\b[^>]*?)(?<!/)>";
UISceneNode* UISceneNode::New( EE::Window::Window* window ) {
return eeNew( UISceneNode, ( window ) );
}
@@ -543,26 +539,14 @@ UIWidget* UISceneNode::loadLayoutFromFile( const std::string& layoutPath, Node*
UIWidget* UISceneNode::loadLayoutFromString( const char* layoutString, Node* parent,
const Uint32& marker ) {
RegEx voidTagsRegex( VOIDTAG_REGEX );
pugi::xml_document doc;
pugi::xml_parse_result result;
std::string fixedLayout;
bool needsReplacements = voidTagsRegex.matches( layoutString );
if ( needsReplacements ) {
fixedLayout = voidTagsRegex.gsub( layoutString, "%1 />" );
result =
doc.load_string( fixedLayout.c_str(), pugi::parse_default | pugi::parse_ws_pcdata );
} else {
result = doc.load_string( layoutString, pugi::parse_default | pugi::parse_ws_pcdata );
}
pugi::xml_parse_result result =
doc.load_string( layoutString, pugi::parse_default | pugi::parse_ws_pcdata );
if ( result ) {
return loadLayoutNodes( doc.first_child(), NULL != parent ? parent : this, marker );
} else {
Log::error( "Couldn't load UI Layout from string: %s",
needsReplacements ? fixedLayout.c_str() : layoutString );
Log::error( "Couldn't load UI Layout from string: %s", layoutString );
Log::error( "Error description: %s", result.description() );
Log::error( "Error offset: %d", result.offset );
Log::error( "Error context: %s", getErrorContext( result.offset, layoutString ) );
@@ -578,28 +562,15 @@ UIWidget* UISceneNode::loadLayoutFromString( const std::string& layoutString, No
UIWidget* UISceneNode::loadLayoutFromMemory( const void* buffer, Int32 bufferSize, Node* parent,
const Uint32& marker ) {
RegEx voidTagsRegex( VOIDTAG_REGEX );
pugi::xml_document doc;
pugi::xml_parse_result result;
std::string_view layoutString( static_cast<const char*>( buffer ), bufferSize );
std::string fixedLayout;
bool needsReplacements =
voidTagsRegex.matches( static_cast<const char*>( buffer ), 0, nullptr, bufferSize );
if ( needsReplacements ) {
fixedLayout = voidTagsRegex.gsub( layoutString.data(), "%1 />" );
result = doc.load_buffer( fixedLayout.c_str(), fixedLayout.size(),
pugi::parse_default | pugi::parse_ws_pcdata );
} else {
result = doc.load_buffer( buffer, bufferSize, pugi::parse_default | pugi::parse_ws_pcdata );
}
pugi::xml_parse_result result =
doc.load_buffer( buffer, bufferSize, pugi::parse_default | pugi::parse_ws_pcdata );
if ( result ) {
return loadLayoutNodes( doc.first_child(), NULL != parent ? parent : this, marker );
} else {
Log::error( "Couldn't load UI Layout from memory: %s",
needsReplacements ? fixedLayout.c_str() : layoutString.data() );
Log::error( "Couldn't load UI Layout from memory: %s", layoutString.data() );
Log::error( "Error description: %s", result.description() );
Log::error( "Error offset: %d", result.offset );
Log::error( "Error context: %s",
@@ -620,32 +591,17 @@ UIWidget* UISceneNode::loadLayoutFromStream( IOStream& stream, Node* parent,
TScopedBuffer<char> scopedBuffer( bufferSize );
stream.read( scopedBuffer.get(), scopedBuffer.length() );
RegEx voidTagsRegex( VOIDTAG_REGEX );
pugi::xml_document doc;
pugi::xml_parse_result result;
std::string_view layoutString( scopedBuffer.get(), scopedBuffer.length() );
std::string fixedLayout;
bool needsReplacements =
voidTagsRegex.matches( scopedBuffer.get(), 0, nullptr, scopedBuffer.length() );
std::string_view contents;
if ( needsReplacements ) {
fixedLayout = voidTagsRegex.gsub( layoutString.data(), "%1 />" );
result = doc.load_buffer( fixedLayout.c_str(), fixedLayout.size(),
pugi::parse_default | pugi::parse_ws_pcdata );
contents = fixedLayout;
} else {
result = doc.load_buffer( scopedBuffer.get(), scopedBuffer.length(),
pugi::xml_parse_result result = doc.load_buffer( scopedBuffer.get(), scopedBuffer.length(),
pugi::parse_default | pugi::parse_ws_pcdata );
contents = std::string_view( scopedBuffer.get(), scopedBuffer.length() );
}
if ( result ) {
return loadLayoutNodes( doc.first_child(), NULL != parent ? parent : this, marker );
} else {
Log::error( "Couldn't load UI Layout from stream: %s",
needsReplacements ? fixedLayout.c_str() : layoutString.data() );
Log::error( "Couldn't load UI Layout from stream: %s", layoutString.data() );
Log::error( "Error description: %s", result.description() );
Log::error( "Error offset: %d", result.offset );
Log::error( "Error context: %s", getErrorContext( result.offset, contents ) );
@@ -1531,6 +1487,8 @@ Font* UISceneNode::getFontFromNamesList( std::string_view names, Uint32 fontStyl
font = fm->getByName( fontFamily );
// Remove the font style part (ex: `Arial#bold` to `Arial`)
// We need this for SystemFontResolver::genericFamilyFromName
if ( fontStyle )
fontFamily.resize( size );
@@ -1548,12 +1506,9 @@ Font* UISceneNode::getFontFromNamesList( std::string_view names, Uint32 fontStyl
font = fm->getByName( fontFamily );
}
return font != nullptr;
},
',' );
if ( font == nullptr && SystemFontResolver::isEnabled() ) {
FontWeight weight = ( fontStyle & Text::Bold ) ? FontWeight::Bold : FontWeight::Normal;
FontWeight weight =
( fontStyle & Text::Bold ) ? FontWeight::Bold : FontWeight::Normal;
FontDesc desc = SystemFontResolver::instance()->resolveFromNamesList(
std::string{ names }, weight, fontStyle & Text::Italic );
if ( !desc.path.empty() ) {
@@ -1562,7 +1517,7 @@ Font* UISceneNode::getFontFromNamesList( std::string_view names, Uint32 fontStyl
family += "#" + Text::styleFlagToString( fontStyle );
if ( ( font = fm->getByName( family ) ) )
return font;
return true;
FontTrueType* ttf = FontTrueType::New( family, desc.path, desc.faceIndex );
if ( ttf && ttf->loaded() ) {
@@ -1570,7 +1525,8 @@ Font* UISceneNode::getFontFromNamesList( std::string_view names, Uint32 fontStyl
Uint32 weightStyle = fontStyle & ( Text::Bold | Text::Italic );
if ( weightStyle ) {
Font* regular = fm->getByName( desc.family );
if ( regular && regular != font && regular->getType() == FontType::TTF ) {
if ( regular && regular != font &&
regular->getType() == FontType::TTF ) {
auto* regularFT = static_cast<FontTrueType*>( regular );
if ( weightStyle == Text::Bold )
regularFT->setBoldFont( ttf );
@@ -1584,6 +1540,10 @@ Font* UISceneNode::getFontFromNamesList( std::string_view names, Uint32 fontStyl
}
}
return font != nullptr;
},
',' );
return font;
}

View File

@@ -1118,7 +1118,7 @@ UTEST( UIHTMLBody, maxWidthResizingBug ) {
std::string htmlContent;
FileSystem::fileGet( "assets/html/dwarmstrong/dwarmstrong.html", htmlContent );
sceneNode->loadLayoutFromString( htmlContent );
sceneNode->loadLayoutFromString( HTMLFormatter::HTMLtoXML( htmlContent ) );
sceneNode->getRoot()->setSize( 1024, 768 );
sceneNode->updateDirtyLayouts();