Several crashes fixes for HTML loading.

This commit is contained in:
Martín Lucas Golini
2026-05-15 18:59:51 -03:00
parent 852adaa2c5
commit a6b9f39ac3
14 changed files with 306 additions and 187 deletions

View File

@@ -274,6 +274,12 @@ class EE_API FontTrueType : public Font {
bool setFontFace( void* face );
void updateMonospaceState() const;
void disconnectBoldFont();
void disconnectItalicFont();
void disconnectBoldItalicFont();
};
}} // namespace EE::Graphics

View File

@@ -105,8 +105,6 @@ Font* FontManager::getByInternalId( Uint32 internalId ) const {
}
FontTrueType* FontManager::getOrLoadSystemFallbackFont( const FontDesc& desc ) {
static constexpr Uint32 MAX_SYSTEM_FALLBACK_FONTS = 32;
if ( desc.path.empty() )
return nullptr;
@@ -126,12 +124,6 @@ FontTrueType* FontManager::getOrLoadSystemFallbackFont( const FontDesc& desc ) {
mSystemFallbackFonts.push_back( ttf );
if ( mSystemFallbackFonts.size() > MAX_SYSTEM_FALLBACK_FONTS ) {
Font* oldest = mSystemFallbackFonts.front();
mSystemFallbackFonts.erase( mSystemFallbackFonts.begin() );
eeSAFE_DELETE( oldest );
}
return ttf;
}

View File

@@ -359,10 +359,12 @@ bool FontTrueType::loadFromFile( const std::string& filename, Uint32 faceIndex )
// Load the new font face from the specified file
FT_Face face;
if ( FT_New_Face( static_cast<FT_Library>( mLibrary ), filename.c_str(),
static_cast<FT_Long>( mFaceIndex ), &face ) != 0 ) {
Log::error( "Failed to load font \"%s\" (%s) (failed to create the font face)",
filename.c_str(), mFontName.c_str() );
FT_Error err = FT_New_Face( static_cast<FT_Library>( mLibrary ), filename.c_str(),
static_cast<FT_Long>( mFaceIndex ), &face );
if ( err != 0 ) {
const char* err_str = FT_Error_String( err );
Log::error( "Failed to load font \"%s\" (%s, face index %zu) - %s (code %d)", filename,
mFontName, mFaceIndex, err_str ? err_str : "Unknown error", err );
return false;
}
@@ -397,11 +399,14 @@ bool FontTrueType::loadFromMemory( const void* data, std::size_t sizeInBytes, bo
// Load the new font face from the specified file
FT_Face face;
if ( FT_New_Memory_Face( static_cast<FT_Library>( mLibrary ),
reinterpret_cast<const FT_Byte*>( ptr ),
static_cast<FT_Long>( sizeInBytes ),
static_cast<FT_Long>( mFaceIndex ), &face ) != 0 ) {
Log::error( "Failed to load font from memory (failed to create the font face)" );
FT_Error err = FT_New_Memory_Face(
static_cast<FT_Library>( mLibrary ), reinterpret_cast<const FT_Byte*>( ptr ),
static_cast<FT_Long>( sizeInBytes ), static_cast<FT_Long>( mFaceIndex ), &face );
if ( err != 0 ) {
const char* err_str = FT_Error_String( err );
Log::error( "Failed to load font from memory (failed to create the font face, face index "
"%zu): %s (code %d)",
mFaceIndex, err_str ? err_str : "Unknown error", err );
return false;
}
@@ -1071,20 +1076,9 @@ void FontTrueType::cleanup() {
if ( FontManager::existsSingleton() && FontManager::instance()->getColorEmojiFont() == this )
FontManager::instance()->setColorEmojiFont( nullptr );
if ( mFontBoldItalicCb != 0 && mFontBoldItalic != nullptr ) {
mFontBoldItalic->popFontEventCallback( mFontBoldItalicCb );
mFontBoldItalicCb = 0;
}
if ( mFontBoldCb != 0 && mFontBold != nullptr ) {
mFontBold->popFontEventCallback( mFontBoldCb );
mFontBoldCb = 0;
}
if ( mFontItalicCb != 0 && mFontItalic != nullptr ) {
mFontItalic->popFontEventCallback( mFontItalicCb );
mFontItalicCb = 0;
}
disconnectBoldItalicFont();
disconnectBoldFont();
disconnectItalicFont();
mCallbacks.clear();
mNumCallBacks = 0;
@@ -1772,6 +1766,7 @@ void FontTrueType::updateMonospaceState() const {
void FontTrueType::setBoldFont( FontTrueType* fontBold ) {
if ( fontBold == mFontBold )
return;
disconnectBoldFont();
mFontBold = fontBold;
if ( mFontBold != nullptr ) {
mFontBoldCb = mFontBold->pushFontEventCallback( [this]( Uint32, Event event, Font* ) {
@@ -1788,6 +1783,7 @@ void FontTrueType::setBoldFont( FontTrueType* fontBold ) {
void FontTrueType::setItalicFont( FontTrueType* fontItalic ) {
if ( fontItalic == mFontItalic )
return;
disconnectItalicFont();
mFontItalic = fontItalic;
if ( mFontItalic != nullptr ) {
mFontItalicCb = mFontItalic->pushFontEventCallback( [this]( Uint32, Event event, Font* ) {
@@ -1804,6 +1800,7 @@ void FontTrueType::setItalicFont( FontTrueType* fontItalic ) {
void FontTrueType::setBoldItalicFont( FontTrueType* fontBoldItalic ) {
if ( fontBoldItalic == mFontBoldItalic )
return;
disconnectBoldItalicFont();
mFontBoldItalic = fontBoldItalic;
if ( mFontBoldItalic != nullptr ) {
mFontBoldItalicCb =
@@ -1818,6 +1815,27 @@ void FontTrueType::setBoldItalicFont( FontTrueType* fontBoldItalic ) {
updateMonospaceState();
}
void FontTrueType::disconnectBoldFont() {
if ( mFontBoldCb != 0 && mFontBold != nullptr )
mFontBold->popFontEventCallback( mFontBoldCb );
mFontBold = nullptr;
mFontBoldCb = 0;
}
void FontTrueType::disconnectItalicFont() {
if ( mFontItalicCb != 0 && mFontItalic != nullptr )
mFontItalic->popFontEventCallback( mFontItalicCb );
mFontItalic = nullptr;
mFontItalicCb = 0;
}
void FontTrueType::disconnectBoldItalicFont() {
if ( mFontBoldItalicCb != 0 && mFontBoldItalic != nullptr )
mFontBoldItalic->popFontEventCallback( mFontBoldItalicCb );
mFontBoldItalic = nullptr;
mFontBoldItalicCb = 0;
}
bool FontTrueType::hasSvgGlyphs() const {
return mHasSvgGlyphs;
}

View File

@@ -1320,6 +1320,11 @@ void Image::copyImage( Graphics::Image* image, const Uint32& x, const Uint32& y
}
void Image::resize( const Uint32& newWidth, const Uint32& newHeight, ResamplerFilter filter ) {
if ( newWidth == 0 || newHeight == 0 ) {
Log::warning( "Image::resize: Invalid resize %dx%d", newWidth, newHeight ) ;
return;
}
if ( NULL != mPixels && ( mWidth != newWidth || mHeight != newHeight ) ) {
unsigned char* resampled =
resample_image( mPixels, mWidth, mHeight, mChannels, newWidth, newHeight, filter );

View File

@@ -1,5 +1,6 @@
#include <algorithm>
#include <cctype>
#include <cstring>
#include <eepp/network/http.hpp>
#include <eepp/network/http/httpstreamchunked.hpp>
#include <eepp/network/ssl/sslsocket.hpp>
@@ -10,6 +11,7 @@
#include <eepp/system/iostreamfile.hpp>
#include <eepp/system/iostreaminflate.hpp>
#include <eepp/system/iostreamstring.hpp>
#include <eepp/system/log.hpp>
#include <eepp/system/sys.hpp>
#include <iostream>
#include <iterator>
@@ -661,6 +663,11 @@ Uint64 Http::requestAsync( const Http::AsyncResponseCallback& cb, const URI& uri
const Http::Request::FieldTable& headers, const std::string& body,
const bool& validateCertificate, const URI& proxy,
bool followRedirect ) {
if ( Log::existsSingleton() && Log::instance()->getLogLevelThreshold() == LogLevel::Debug ) {
Log::debug( "Http::requestAsync: %s \"%s\"", Request::methodToString( method ),
uri.toString() );
}
auto http = sGlobalHttpPool.get( uri, proxy );
Request request( uri.getPathAndQuery(), method, body, validateCertificate, validateCertificate,
true, true );
@@ -978,8 +985,6 @@ Http::Response Http::downloadRequest( const Http::Request& request, IOStream& wr
std::size_t currentTotalBytes = 0;
std::size_t len = 0;
std::size_t read = 0;
char* eol = NULL; // end of line
char* bol = NULL; // beginning of line
char buffer[PACKET_BUFFER_SIZE + 1];
bool isnheader = false;
bool chunked = false;
@@ -998,173 +1003,147 @@ Http::Response Http::downloadRequest( const Http::Request& request, IOStream& wr
// If we didn't receive the header yet, we will try to find the end of the
// header
if ( !isnheader ) {
// calculate combined length of unprocessed data and new data
len += read;
headerBuffer.append( readBuffer, read );
// NULL terminate buffer for string functions
readBuffer[len] = '\0';
std::size_t headerEnd = headerBuffer.find( "\r\n\r\n" );
std::size_t headerDelimiterSize = 4;
// process each line in buffer looking for header break
bol = readBuffer;
if ( headerEnd == std::string::npos ) {
headerEnd = headerBuffer.find( "\n\n" );
headerDelimiterSize = 2;
}
while ( !isnheader && ( eol = strchr( bol, '\n' ) ) != NULL ) {
// update bol based upon the value of eol
bol = eol + 1;
if ( headerEnd != std::string::npos ) {
isnheader = true;
headerEnd += headerDelimiterSize;
len = headerBuffer.size() - headerEnd;
// test if end of headers has been reached
if ( 0 == strncmp( bol, "\r\n", 2 ) || 0 == strncmp( bol, "\n", 1 ) ) {
// note that end of headers has been reached
isnheader = true;
std::string responseHead( headerBuffer, 0, headerEnd );
// update the value of bol to reflect the beginning of the line
// immediately after the headers
if ( bol[0] != '\n' )
bol += 1;
if ( !responseHead.empty() ) {
// Build the Response object from the received data
received.parse( responseHead );
bol += 1;
// Check if the response is chunked
chunked = received.getField( "transfer-encoding" ) == "chunked";
// calculate the amount of data remaining in the buffer
len = read - ( bol - readBuffer );
// Check if the content is compressed
std::string encoding( received.getField( "content-encoding" ) );
compressed =
encoding == "gzip" || encoding == "deflate" || encoding == "br";
// Fill the header buffer
headerBuffer.append( readBuffer, ( bol - readBuffer ) );
if ( compressed ) {
Compression::Mode compressionMode =
"gzip" == encoding
? Compression::MODE_GZIP
: ( "br" == encoding ? Compression::MODE_BROTLI
: Compression::MODE_DEFLATE );
if ( !headerBuffer.empty() ) {
// Build the Response object from the received data
received.parse( headerBuffer );
inflateStream =
IOStreamInflate::New( writeTo, compressionMode );
}
// Check if the response is chunked
chunked = received.getField( "transfer-encoding" ) == "chunked";
if ( chunked ) {
IOStream& writeToStream = compressed ? *inflateStream : writeTo;
chunkedStream = eeNew( HttpStreamChunked, ( writeToStream ) );
}
// Check if the content is compressed
std::string encoding( received.getField( "content-encoding" ) );
compressed = encoding == "gzip" || encoding == "deflate" ||
encoding == "br";
if ( compressed ) {
Compression::Mode compressionMode =
"gzip" == encoding
? Compression::MODE_GZIP
: ( "br" == encoding ? Compression::MODE_BROTLI
: Compression::MODE_DEFLATE );
inflateStream =
IOStreamInflate::New( writeTo, compressionMode );
}
if ( chunked ) {
IOStream& writeToStream =
compressed ? *inflateStream : writeTo;
chunkedStream =
eeNew( HttpStreamChunked, ( writeToStream ) );
}
bufferStream = chunked
? chunkedStream
bufferStream = chunked ? chunkedStream
: ( compressed ? inflateStream : &writeTo );
// Get the content length
if ( !received.getField( "content-length" ).empty() ) {
if ( !String::fromString(
contentLength,
received.getField( "content-length" ) ) )
contentLength = 0;
}
// Get the content length
if ( !received.getField( "content-length" ).empty() ) {
if ( !String::fromString(
contentLength,
received.getField( "content-length" ) ) )
contentLength = 0;
}
if ( mConnection &&
received.getField( "connection" ) == "closed" ) {
mConnection->setConnected( false );
mConnection->setTunneled( false );
}
if ( mConnection &&
received.getField( "connection" ) == "closed" ) {
mConnection->setConnected( false );
mConnection->setTunneled( false );
}
// If a redirection is requested, and requests follows
// redirections, send a new request to the redirection location.
if ( ( received.getStatus() == Response::MovedPermanently ||
received.getStatus() == Response::MovedTemporarily ||
received.getStatus() == Response::PermanentRedirect ||
received.getStatus() == Response::TemporaryRedirect ) &&
request.getFollowRedirect() ) {
// If a redirection is requested, and requests follows
// redirections, send a new request to the redirection location.
if ( ( received.getStatus() == Response::MovedPermanently ||
received.getStatus() == Response::MovedTemporarily ||
received.getStatus() == Response::PermanentRedirect ||
received.getStatus() == Response::TemporaryRedirect ) &&
request.getFollowRedirect() ) {
// Only continue redirecting if less than 10 redirections
// were done
if ( request.mRedirectionCount <
request.getMaxRedirects() ) {
std::string location( received.getField( "location" ) );
URI uri( location );
// Only continue redirecting if less than 10 redirections
// were done
if ( request.mRedirectionCount < request.getMaxRedirects() ) {
std::string location( received.getField( "location" ) );
URI uri( location );
// Close the connection
if ( mConnection && !mConnection->isKeepAlive() )
mConnection->disconnect();
// Close the connection
if ( mConnection && !mConnection->isKeepAlive() )
mConnection->disconnect();
eeSAFE_DELETE( chunkedStream );
eeSAFE_DELETE( inflateStream );
eeSAFE_DELETE( chunkedStream );
eeSAFE_DELETE( inflateStream );
if ( !request.isCancelled() &&
!sendProgress( *this, request, received,
Request::Redirect, contentLength,
currentTotalBytes ) ) {
request.mCancel = true;
if ( !request.isCancelled() &&
!sendProgress( *this, request, received,
Request::Redirect, contentLength,
currentTotalBytes ) ) {
request.mCancel = true;
} else {
Http::Request newRequest( request );
newRequest.setUri( uri.getPathAndQuery() );
newRequest.setMethod(
Http::Request::getRedirectMethodFromStatus(
request.getMethod(), received.getStatus() ) );
newRequest.setProgressCallback(
request.getProgressCallback() );
if ( received.hasField( "set-cookie" ) ) {
newRequest.setField(
"Cookie", received.getField( "set-cookie" ) );
}
request.mRedirectionCount++;
newRequest.mRedirectionCount =
request.mRedirectionCount;
// Same host, expects a path in the same domain
if ( uri.getHost().empty() ||
uri.getHost() == getHostName() ) {
return downloadRequest( newRequest, writeTo,
timeout );
} else {
Http::Request newRequest( request );
newRequest.setUri( uri.getPathAndQuery() );
newRequest.setMethod(
Http::Request::getRedirectMethodFromStatus(
request.getMethod(),
received.getStatus() ) );
newRequest.setProgressCallback(
request.getProgressCallback() );
if ( received.hasField( "set-cookie" ) ) {
newRequest.setField(
"Cookie",
received.getField( "set-cookie" ) );
}
request.mRedirectionCount++;
newRequest.mRedirectionCount =
request.mRedirectionCount;
// Same host, expects a path in the same domain
if ( uri.getHost().empty() ||
uri.getHost() == getHostName() ) {
return downloadRequest( newRequest, writeTo,
timeout );
} else {
// New host, we need to solve the host
Http http( uri.getHost(), uri.getPort(),
uri.getScheme() == "https" ? true
: false );
return http.downloadRequest( newRequest,
writeTo, timeout );
}
// New host, we need to solve the host
Http http( uri.getHost(), uri.getPort(),
uri.getScheme() == "https" ? true
: false );
return http.downloadRequest( newRequest, writeTo,
timeout );
}
}
}
if ( !request.isCancelled() &&
!sendProgress( *this, request, received,
Request::HeaderReceived, contentLength,
0 ) ) {
request.mCancel = true;
}
// Move the readBuffer to the starting point
// of the file buffer
if ( len > 0 ) {
readBuffer = bol;
read = len;
} else {
read = 0;
}
headerBuffer.clear();
}
}
}
if ( !isnheader ) {
headerBuffer.append( readBuffer, ( bol - readBuffer ) );
if ( !request.isCancelled() &&
!sendProgress( *this, request, received,
Request::HeaderReceived, contentLength, 0 ) ) {
request.mCancel = true;
}
// Move the response body bytes already read into the socket buffer.
if ( len > 0 ) {
std::memmove( buffer, headerBuffer.data() + headerEnd, len );
readBuffer = buffer;
read = len;
} else {
read = 0;
}
headerBuffer.clear();
}
}
}

View File

@@ -11,8 +11,8 @@
#endif
#ifdef _MSC_VER
#pragma warning( \
disable : 4127 ) // "conditional expression is constant" generated by the FD_SET macro
#pragma warning( disable \
: 4127 ) // "conditional expression is constant" generated by the FD_SET macro
#endif
namespace {
@@ -129,6 +129,13 @@ Socket::Status TcpSocket::connect( const IpAddress& remoteAddress, unsigned shor
// Otherwise, wait until something happens to our socket (success, timeout or error)
if ( status == Socket::NotReady ) {
if ( getHandle() >= FD_SETSIZE ) {
// The socket FD is too large for select().
// You cannot safely use FD_SET.
setBlocking( true );
return Error;
}
// Setup the selector
fd_set selector;
FD_ZERO( &selector );

View File

@@ -484,6 +484,8 @@ Uint32 UIRichText::getFontSize() const {
UIRichText* UIRichText::setFontSize( const Uint32& characterSize ) {
if ( mRichText.getFontStyleConfig().CharacterSize != characterSize ) {
if ( characterSize == 0 )
return this;
mRichText.getFontStyleConfig().CharacterSize = characterSize;
mRichText.invalidate();
mLineHeightPxDirty = true;
@@ -745,6 +747,12 @@ void UIRichText::loadFromXmlNode( const pugi::xml_node& node ) {
} else {
// Let parent logic load standard child widget
UIWidget* uiwidget = UIWidgetCreator::createFromName( child.name() );
// For not known elements it should create an HTMLUnknown element, in practice
// an span with a the tag name used is enough
if ( uiwidget == nullptr )
uiwidget = UITextSpan::NewWithTag( child.name() );
if ( uiwidget ) {
uiwidget->setParent( this );
uiwidget->loadFromXmlNode( child );

View File

@@ -1190,9 +1190,11 @@ void UISceneNode::loadFontFaces( const StyleSheetStyleVector& styles, URI baseUR
Base64::decode( data, decoded );
FontTrueType* font = FontTrueType::New( familyName );
if ( font->loadFromMemory( &decoded[0], decoded.size() ) ) {
trySetFontFamily( fontFamily, fontStyle, font );
mFontFaces.push_back( font );
runOnMainThread( [this] { mRoot->reloadFontFamily(); } );
runOnMainThread( [this, trySetFontFamily, fontFamily, fontStyle, font] {
trySetFontFamily( fontFamily, fontStyle, font );
mFontFaces.push_back( font );
mRoot->reloadFontFamily();
} );
} else
eeSAFE_DELETE( font );
}
@@ -1517,18 +1519,38 @@ Font* UISceneNode::getFontFromNamesList( std::string_view names, Uint32 fontStyl
name = String::trim( name, ' ' );
name = String::trim( name, '\'' );
name = String::trim( name, '"' );
std::string fontFamily{ name };
size_t size = fontFamily.size();
if ( fontStyle )
fontFamily += "#" + Text::styleFlagToString( fontStyle );
font = FontManager::instance()->getByName( fontFamily );
if ( fontStyle )
fontFamily.resize( size );
if ( font == nullptr &&
SystemFontResolver::genericFamilyFromName( fontFamily ) != GenericFamily::None ) {
FontQuery query;
query.family = fontFamily;
query.italic = fontStyle & Text::Italic;
query.weight = ( fontStyle & Text::Bold ) ? FontWeight::Bold : FontWeight::Normal;
fontFamily = SystemFontResolver::instance()->resolve( query ).family;
if ( fontStyle )
fontFamily += "#" + Text::styleFlagToString( fontStyle );
font = FontManager::instance()->getByName( fontFamily );
}
return font != nullptr;
},
',' );
if ( !font && Graphics::SystemFontResolver::isEnabled() ) {
Graphics::FontWeight weight =
( fontStyle & Text::Bold ) ? Graphics::FontWeight::Bold : Graphics::FontWeight::Normal;
Graphics::FontDesc desc = Graphics::SystemFontResolver::instance()->resolveFromNamesList(
if ( font == nullptr && SystemFontResolver::isEnabled() ) {
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() ) {
std::string family = desc.family;
@@ -1559,7 +1581,7 @@ Font* UISceneNode::getFontFromNamesList( std::string_view names, Uint32 fontStyl
}
Font* UISceneNode::reevaluateFontStyle( Font* currentFont, Uint32 fontStyle ) const {
if ( !currentFont || !Graphics::SystemFontResolver::isEnabled() )
if ( !currentFont || !SystemFontResolver::isEnabled() )
return nullptr;
if ( currentFont->getType() != FontType::TTF )
@@ -1579,7 +1601,7 @@ Font* UISceneNode::reevaluateFontStyle( Font* currentFont, Uint32 fontStyle ) co
}
void UISceneNode::loadFontStyleVariants( Font* font, const std::string& family ) const {
if ( !font || !Graphics::SystemFontResolver::isEnabled() )
if ( !font || !SystemFontResolver::isEnabled() )
return;
if ( font->getType() != FontType::TTF )
return;

View File

@@ -231,6 +231,8 @@ Uint32 UITextSpan::getFontSize() const {
UITextSpan* UITextSpan::setFontSize( const Uint32& characterSize ) {
if ( mRichText.getFontStyleConfig().CharacterSize != characterSize ) {
if ( characterSize == 0 )
return this;
mRichText.getFontStyleConfig().CharacterSize = characterSize;
mStyleState |= StyleStateFontSize;
mRichText.invalidate();
@@ -414,6 +416,10 @@ void UITextSpan::loadFromXmlNode( const pugi::xml_node& node ) {
for ( pugi::xml_node child = node.first_child(); child; child = child.next_sibling() ) {
if ( child.type() == pugi::node_element ) {
UIWidget* widget = UIWidgetCreator::createFromName( child.name() );
if ( widget == nullptr )
widget = UITextSpan::NewWithTag( child.name() );
if ( widget ) {
widget->setParent( this );
widget->loadFromXmlNode( child );

View File

@@ -4,7 +4,11 @@
#include <iostream>
EE_MAIN_FUNC int main( int argc, char** argv ) {
std::shared_ptr<ThreadPool> threadPool(
ThreadPool::createShared( eemax<int>( 4, Sys::getCPUCount() ) ) );
Http::setThreadPool( threadPool );
SystemFontResolver::setEnabled( true );
args::ArgumentParser parser( "eepp HTML Example" );
args::HelpFlag help( parser, "help", "Display this help menu", { 'h', "help" } );

View File

@@ -1,6 +1,5 @@
#include "utest.h"
#include <eepp/network/http.hpp>
#include <eepp/network/cookiemanager.hpp>
using namespace EE;

View File

@@ -34,6 +34,36 @@ using namespace EE::Graphics;
using namespace EE::Window;
using namespace EE::UI;
UTEST( FontRendering, relatedFontsDisconnectReplacedCallbacks ) {
FontTrueType* font = FontTrueType::New( "RelatedFontsDisconnect-Regular" );
FontTrueType* oldBold = FontTrueType::New( "RelatedFontsDisconnect-OldBold" );
FontTrueType* newBold = FontTrueType::New( "RelatedFontsDisconnect-NewBold" );
FontTrueType* oldItalic = FontTrueType::New( "RelatedFontsDisconnect-OldItalic" );
FontTrueType* newItalic = FontTrueType::New( "RelatedFontsDisconnect-NewItalic" );
FontTrueType* oldBoldItalic = FontTrueType::New( "RelatedFontsDisconnect-OldBoldItalic" );
FontTrueType* newBoldItalic = FontTrueType::New( "RelatedFontsDisconnect-NewBoldItalic" );
font->setBoldFont( oldBold );
font->setBoldFont( newBold );
eeDelete( oldBold );
EXPECT_EQ( newBold, font->getBoldFont() );
font->setItalicFont( oldItalic );
font->setItalicFont( newItalic );
eeDelete( oldItalic );
EXPECT_EQ( newItalic, font->getItalicFont() );
font->setBoldItalicFont( oldBoldItalic );
font->setBoldItalicFont( newBoldItalic );
eeDelete( oldBoldItalic );
EXPECT_EQ( newBoldItalic, font->getBoldItalicFont() );
eeDelete( font );
eeDelete( newBold );
eeDelete( newItalic );
eeDelete( newBoldItalic );
}
UTEST( FontRendering, fontsTest ) {
FileSystem::changeWorkingDirectory( Sys::getProcessPath() );

View File

@@ -0,0 +1,38 @@
#include "utest.h"
#include <atomic>
#include <thread>
#include <eepp/network/http.hpp>
#include <eepp/network/tcplistener.hpp>
#include <eepp/network/tcpsocket.hpp>
using namespace EE;
using namespace EE::Network;
UTEST( Http, responseHeaderLineLargerThanReceiveBuffer ) {
TcpListener listener;
ASSERT_EQ( listener.listen( Socket::AnyPort, IpAddress::LocalHost ), Socket::Done );
std::atomic<bool> serverOk{ false };
std::thread server( [&listener, &serverOk] {
TcpSocket client;
if ( listener.accept( client ) != Socket::Done )
return;
const std::string response = "HTTP/1.1 200 OK\r\nX-Long: " + std::string( 17000, 'a' ) +
"\r\nContent-Length: 5\r\nConnection: close\r\n\r\nhello";
serverOk = client.send( response.data(), response.size() ) == Socket::Done;
client.disconnect();
} );
Http http( "127.0.0.1", listener.getLocalPort() );
Http::Response response = http.sendRequest( Http::Request( "/" ), Seconds( 5 ) );
server.join();
listener.close();
EXPECT_TRUE( serverOk );
EXPECT_EQ( response.getStatus(), Http::Response::Ok );
EXPECT_TRUE( response.getBody() == "hello" );
}

View File

@@ -131,6 +131,11 @@ UTEST( UIHTMLTable, complexLayout2 ) {
win->getInput()->update();
SceneManager::instance()->update();
SceneManager::instance()->update();
win->clear();
SceneManager::instance()->draw();
win->display();
win->clear();
SceneManager::instance()->draw();