diff --git a/bin/unit_tests/assets/fontrendering/eepp-text-hard-wrap.webp b/bin/unit_tests/assets/fontrendering/eepp-text-hard-wrap.webp new file mode 100644 index 000000000..84550af56 Binary files /dev/null and b/bin/unit_tests/assets/fontrendering/eepp-text-hard-wrap.webp differ diff --git a/bin/unit_tests/assets/textfiles/test-hard-wrap.uext b/bin/unit_tests/assets/textfiles/test-hard-wrap.uext new file mode 100644 index 000000000..81b7d65c7 --- /dev/null +++ b/bin/unit_tests/assets/textfiles/test-hard-wrap.uext @@ -0,0 +1,14 @@ +```go +func fmt.Errorf(format string, a ...any) error +``` + +--- + +Errorf formats according to a format specifier and returns the string as a value that satisfies error. + +If the format specifier includes a %w verb with an error operand, the returned error will implement an Unwrap method returning the operand. If there is more than one %w verb, the returned error will implement an Unwrap method returning a \[]error containing all the %w operands in the order they appear in the arguments. It is invalid to supply the %w verb with an operand that does not implement the error interface. The %w verb is otherwise a synonym for %v. + + +--- + +[`fmt.Errorf` on pkg.go.dev](https://pkg.go.dev/fmt#Errorf) diff --git a/include/eepp/graphics/linewrap.hpp b/include/eepp/graphics/linewrap.hpp index cf52d953c..c15d677bd 100644 --- a/include/eepp/graphics/linewrap.hpp +++ b/include/eepp/graphics/linewrap.hpp @@ -27,28 +27,31 @@ class EE_API LineWrap { static bool isWrapChar( String::StringBaseType ch ); - static LineWrapInfo computeLineBreaks( - const String::View& string, Font* font, Uint32 characterSize, Float maxWidth, - LineWrapMode mode, Uint32 fontStyle, Float outlineThickness, bool keepIndentation, - Uint32 tabWidth = 4, Float whiteSpaceWidth = 0.f /* 0 = should calculate it */, - Uint32 textHints = TextHints::None, bool tabStops = false, Float initialXOffset = 0.f ); + static LineWrapInfo + computeLineBreaks( const String::View& string, Font* font, Uint32 characterSize, Float maxWidth, + LineWrapMode mode, Uint32 fontStyle = 0, Float outlineThickness = 0.f, + bool keepIndentation = false, Uint32 tabWidth = 4, + Float whiteSpaceWidth = 0.f /* 0 = should calculate it */, + Uint32 textHints = TextHints::None, bool tabStops = false, + Float initialXOffset = 0.f ); static LineWrapInfo computeLineBreaks( const String& string, Font* font, Uint32 characterSize, Float maxWidth, LineWrapMode mode, - Uint32 fontStyle, Float outlineThickness, bool keepIndentation, Uint32 tabWidth = 4, - Float whiteSpaceWidth = 0.f /* 0 = should calculate it */, + Uint32 fontStyle = 0, Float outlineThickness = 0.f, bool keepIndentation = false, + Uint32 tabWidth = 4, Float whiteSpaceWidth = 0.f /* 0 = should calculate it */, Uint32 textHints = TextHints::None, bool tabStops = false, Float initialXOffset = 0.f ); static LineWrapInfo computeLineBreaks( const String::View& string, const FontStyleConfig& fontStyle, Float maxWidth, - LineWrapMode mode, bool keepIndentation, Uint32 tabWidth = 4, + LineWrapMode mode, bool keepIndentation = false, Uint32 tabWidth = 4, Float whiteSpaceWidth = 0.f /* 0 = should calculate it */, Uint32 textHints = TextHints::None, bool tabStops = false, Float initialXOffset = 0.f ); static LineWrapInfo computeLineBreaks( const String& string, const FontStyleConfig& fontStyle, - Float maxWidth, LineWrapMode mode, bool keepIndentation, - Uint32 tabWidth = 4, Float whiteSpaceWidth = 0.f, + Float maxWidth, LineWrapMode mode, + bool keepIndentation = false, Uint32 tabWidth = 4, + Float whiteSpaceWidth = 0.f, Uint32 textHints = TextHints::None, bool tabStops = false, Float initialXOffset = 0.f ); diff --git a/include/eepp/graphics/text.hpp b/include/eepp/graphics/text.hpp index fccc6d1b2..a625637f1 100644 --- a/include/eepp/graphics/text.hpp +++ b/include/eepp/graphics/text.hpp @@ -156,10 +156,11 @@ class EE_API Text { static bool hardWrapText( Font* font, const Uint32& fontSize, String& string, const Float& maxWidth, const Uint32& style, const Uint32& tabWidth = 4, const Float& outlineThickness = 0.f, - std::optional tabOffset = {} ); + std::optional tabOffset = {}, Uint32 textHints = 0 ); static bool hardWrapText( String& string, const Float& maxWidth, const FontStyleConfig& config, - const Uint32& tabWidth = 4, std::optional tabOffset = {} ); + const Uint32& tabWidth = 4, std::optional tabOffset = {}, + Uint32 textHints = 0 ); static Text* New(); diff --git a/include/eepp/graphics/textshaperun.hpp b/include/eepp/graphics/textshaperun.hpp index 98ed57041..d9d1bdc0c 100644 --- a/include/eepp/graphics/textshaperun.hpp +++ b/include/eepp/graphics/textshaperun.hpp @@ -20,6 +20,8 @@ class EE_API TextShapeRun { std::size_t pos() const; + std::size_t length() const; + void next(); bool runIsNewLine() const; diff --git a/include/eepp/system/color.hpp b/include/eepp/system/color.hpp index 99247644b..a304923b9 100644 --- a/include/eepp/system/color.hpp +++ b/include/eepp/system/color.hpp @@ -148,6 +148,8 @@ template class tColor { typedef tColor ColorAf; typedef tColor Colorf; +class RGB; + class EE_API Color : public tColor { public: Color(); @@ -176,6 +178,8 @@ class EE_API Color : public tColor { Colorf toHsl() const; + RGB toRGB() const; + Color clone() const; Color invert() const; @@ -402,6 +406,8 @@ class EE_API RGB : public tRGB { RGB( const tRGB& color ); + RGB( const Color& color ); + RGB( Uint32 Col ); Color toColor(); diff --git a/src/eepp/graphics/linewrap.cpp b/src/eepp/graphics/linewrap.cpp index 79a3c6d31..b68488b2b 100644 --- a/src/eepp/graphics/linewrap.cpp +++ b/src/eepp/graphics/linewrap.cpp @@ -90,11 +90,18 @@ LineWrapInfo LineWrap::computeLineBreaks( const String::View& string, Font* font info.paddingStart = layout->paragraphs.front().wrapInfo.paddingStart; + std::size_t reserve = 0; + for ( auto& paragraph : layout->paragraphs ) + reserve += paragraph.wrapInfo.wraps.size(); + info.wraps.reserve( reserve ); + for ( auto& paragraph : layout->paragraphs ) { for ( const auto& wrap : paragraph.wrapInfo.wraps ) info.wraps.push_back( wrap ); } + std::sort( info.wraps.begin(), info.wraps.end() ); + return info; } #endif @@ -127,6 +134,7 @@ LineWrapInfo LineWrap::computeLineBreaks( const String::View& string, Font* font if ( curChar == '\n' ) { xoffset = 0; lastSpace = idx; + info.wraps.push_back( lastSpace ); idx++; continue; } diff --git a/src/eepp/graphics/text.cpp b/src/eepp/graphics/text.cpp index 6c8c89ec4..4b750c150 100644 --- a/src/eepp/graphics/text.cpp +++ b/src/eepp/graphics/text.cpp @@ -518,28 +518,31 @@ Sizef Text::draw( const StringType& string, const Vector2f& pos, const FontStyle bool Text::hardWrapText( Font* font, const Uint32& fontSize, String& string, const Float& maxWidth, const Uint32& style, const Uint32& tabWidth, const Float& outlineThickness, - std::optional tabOffset ) { + std::optional tabOffset, Uint32 textHints ) { if ( string.empty() || NULL == font ) return false; LineWrapInfo lineWrap = LineWrap::computeLineBreaks( string, font, fontSize, maxWidth, LineWrapMode::Word, style, outlineThickness, - tabOffset.has_value(), tabWidth, tabOffset ? *tabOffset : 0.f ); + tabOffset.has_value(), tabWidth, tabOffset ? *tabOffset : 0.f, textHints ); if ( lineWrap.wraps.size() <= 1 ) return false; - for ( Int64 i = lineWrap.wraps.size() - 1; i > 0; i-- ) - string.insert( lineWrap.wraps[i], '\n' ); + for ( Int64 i = lineWrap.wraps.size() - 1; i > 0; i-- ) { + if ( string[lineWrap.wraps[i]] != '\n' ) // Skip real line-breaks! + string.insert( lineWrap.wraps[i], '\n' ); + } return true; } bool Text::hardWrapText( String& string, const Float& maxWidth, const FontStyleConfig& config, - const Uint32& tabWidth, std::optional tabOffset ) { + const Uint32& tabWidth, std::optional tabOffset, + Uint32 textHints ) { return hardWrapText( config.Font, config.CharacterSize, string, maxWidth, config.Style, - tabWidth, config.OutlineThickness, tabOffset ); + tabWidth, config.OutlineThickness, tabOffset, textHints ); } Text::Text() {} @@ -1309,7 +1312,7 @@ void Text::updateWidthCache() { } void Text::hardWrapText( const Uint32& maxWidth ) { - if ( hardWrapText( mString, maxWidth, mFontStyleConfig, mTabWidth ) ) + if ( hardWrapText( mString, maxWidth, mFontStyleConfig, mTabWidth, {}, mTextHints ) ) invalidate(); } diff --git a/src/eepp/graphics/textlayout.cpp b/src/eepp/graphics/textlayout.cpp index 3bffbcd64..6d12893be 100644 --- a/src/eepp/graphics/textlayout.cpp +++ b/src/eepp/graphics/textlayout.cpp @@ -273,6 +273,7 @@ TextLayout::Cache TextLayout::layout( const String::View& string, Font* font, TextLayout& result = *resultPtr; result.paragraphs.push_back( {} ); ShapedTextParagraph* curParagraph = &result.paragraphs.back(); + curParagraph->wrapInfo.wraps.push_back( 0 ); struct GlyphDirCounter { int ltr{ 0 }; int rtl{ 0 }; @@ -425,6 +426,7 @@ TextLayout::Cache TextLayout::layout( const String::View& string, Font* font, curParagraph->size.y = pen.y; result.paragraphs.push_back( {} ); curParagraph = &result.paragraphs.back(); + curParagraph->wrapInfo.wraps.push_back( segment.offset + segment.length - 1 ); } return true; } ); @@ -444,6 +446,7 @@ TextLayout::Cache TextLayout::layout( const String::View& string, Font* font, prevChar = 0; result.paragraphs.push_back( {} ); curParagraph = &result.paragraphs.back(); + curParagraph->wrapInfo.wraps.push_back( i ); continue; } if ( curChar == '\r' ) { @@ -502,9 +505,6 @@ TextLayout::Cache TextLayout::layout( const String::View& string, Font* font, if ( wrapMode != LineWrapMode::NoWrap && wrapWidth ) { wrapLayout( string, result, wrapMode, wrapWidth, vspace, keepIndentation, font, characterSize, style, tabWidth, outlineThickness, hspace ); - } else { - for ( auto& sp : result.paragraphs ) - sp.wrapInfo.wraps.push_back( 0 ); } sLayoutCache.put( hash, resultPtr ); @@ -537,13 +537,11 @@ void TextLayout::wrapLayout( const String::View& string, TextLayout& result, const Float& outlineThickness, Float hspace ) { std::size_t paragraphCount = result.paragraphs.size(); - if ( paragraphCount ) - result.paragraphs[0].wrapInfo.wraps.push_back( 0 ); - for ( std::size_t paragraphIdx = 0; paragraphIdx < paragraphCount; paragraphIdx++ ) { ShapedTextParagraph& sp = result.paragraphs[paragraphIdx]; std::size_t shapedGlyphCount = sp.shapedGlyphs.size(); std::size_t lastSpace = std::string::npos; + std::size_t lastSpaceStringIdx = std::string::npos; Vector2f currentOffset( 0.f, 0.f ); Sizef maxSize{ 0, vspace }; @@ -561,21 +559,30 @@ void TextLayout::wrapLayout( const String::View& string, TextLayout& result, if ( sg.position.x + sg.advance.x > wrapWidth ) { std::size_t breakIndex = idx; + std::size_t breakStringIdx = sg.stringIndex; + bool performWordWrap = ( lineWrapMode == LineWrapMode::Word && lastSpace != std::string::npos ); - ShapedGlyph& prevBreakGlyph = sp.shapedGlyphs[lastSpace]; - maxSize.x = - std::max( prevBreakGlyph.position.x + prevBreakGlyph.advance.x, maxSize.x ); + if ( performWordWrap ) { + ShapedGlyph& prevBreakGlyph = sp.shapedGlyphs[lastSpace]; + maxSize.x = + std::max( prevBreakGlyph.position.x + prevBreakGlyph.advance.x, maxSize.x ); + } // Break after the last space (start of the current word) - if ( performWordWrap ) + if ( performWordWrap ) { breakIndex = lastSpace + 1; + breakStringIdx = lastSpaceStringIdx + 1; + } if ( breakIndex > idx ) breakIndex = idx; - sp.wrapInfo.wraps.push_back( breakIndex ); + if ( breakStringIdx > string.size() ) + breakStringIdx = string.size(); + + sp.wrapInfo.wraps.push_back( breakStringIdx ); ShapedGlyph& breakGlyph = sp.shapedGlyphs[breakIndex]; Vector2f adjustment( -breakGlyph.position.x + sp.wrapInfo.paddingStart, vspace ); @@ -587,8 +594,10 @@ void TextLayout::wrapLayout( const String::View& string, TextLayout& result, currentOffset += adjustment; lastSpace = std::string::npos; + lastSpaceStringIdx = std::string::npos; } else if ( LineWrap::isWrapChar( curChar ) ) { lastSpace = idx; + lastSpaceStringIdx = sg.stringIndex; } } diff --git a/src/eepp/graphics/textshaperun.cpp b/src/eepp/graphics/textshaperun.cpp index 748c5582c..20288e73b 100644 --- a/src/eepp/graphics/textshaperun.cpp +++ b/src/eepp/graphics/textshaperun.cpp @@ -36,6 +36,10 @@ std::size_t TextShapeRun::pos() const { return mIndex; } +std::size_t TextShapeRun::length() const { + return mLen; +} + void TextShapeRun::next() { if ( mIsRTL ) { mIndex -= mLen; diff --git a/src/eepp/system/color.cpp b/src/eepp/system/color.cpp index 5683556bf..8f89a2c51 100644 --- a/src/eepp/system/color.cpp +++ b/src/eepp/system/color.cpp @@ -199,6 +199,8 @@ RGB::RGB( Uint8 r, Uint8 g, Uint8 b ) : tRGB( r, g, b ) {} RGB::RGB( const tRGB& color ) : tRGB( color.r, color.g, color.b ) {} +RGB::RGB( const Color& color ) : tRGB( color.r, color.g, color.b ) {} + RGB::RGB( Uint32 Col ) { Col = BitOp::swapLE32( Col ); r = static_cast( Col >> 16 ); @@ -380,6 +382,10 @@ Colorf Color::toHsl() const { return hsl; } +RGB Color::toRGB() const { + return RGB( r, g, b ); +} + Color Color::clone() const { return Color( *this ); } diff --git a/src/tests/unit_tests/fontrendering.cpp b/src/tests/unit_tests/fontrendering.cpp index 23263757d..e1423505b 100644 --- a/src/tests/unit_tests/fontrendering.cpp +++ b/src/tests/unit_tests/fontrendering.cpp @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -965,3 +966,91 @@ UTEST( FontRendering, TextLayoutWrap ) { runTest(); } } + +UTEST( FontRendering, LineWrapInfo ) { + FileSystem::changeWorkingDirectory( Sys::getProcessPath() ); + std::string string; + FileSystem::fileGet( "assets/textfiles/test-hard-wrap.uext", string ); + + UIApplication app( + WindowSettings( 1024, 650, "eepp - LineWrapInfo Test", WindowStyle::Default, + WindowBackend::Default, 32, {}, 1, false, true ), + UIApplication::Settings( Sys::getProcessPath() + ".." + FileSystem::getOSSlash(), 1 ) ); + FileSystem::changeWorkingDirectory( Sys::getProcessPath() ); + + Font* font = app.getUI()->getUIThemeManager()->getDefaultFont(); + Float width = app.getWindow()->getSize().getWidth(); + LineWrapMode mode = LineWrapMode::Word; + + LineWrapInfo lineWrapShaperDisabled; + LineWrapInfo lineWrapShaperEnabled; + { + UTEST_PRINT_STEP( "Text Shaper disabled" ); + BoolScopedOp op( Text::TextShaperEnabled, false ); + String str( string ); + lineWrapShaperDisabled = LineWrap::computeLineBreaks( string, font, 16, width, mode ); + } + + UTEST_PRINT_STEP( "Text Shaper enabled" ); + { + BoolScopedOp op( Text::TextShaperEnabled, true ); + String str( string ); + lineWrapShaperEnabled = LineWrap::computeLineBreaks( string, font, 16, width, mode ); + + { + UTEST_PRINT_STEP( "Text Shaper enabled w/o optimizations" ); + BoolScopedOp op2( Text::TextShaperOptimizations, false ); + String str( string ); + lineWrapShaperEnabled = LineWrap::computeLineBreaks( string, font, 16, width, mode ); + } + } + + EXPECT_VECTOREQ( lineWrapShaperDisabled.wraps, lineWrapShaperEnabled.wraps ); +} + +UTEST( FontRendering, TextHardWrap ) { + FileSystem::changeWorkingDirectory( Sys::getProcessPath() ); + std::string string; + FileSystem::fileGet( "assets/textfiles/test-hard-wrap.uext", string ); + + const auto runTest = [&]() { + UIApplication app( + WindowSettings( 1024, 650, "eepp - Text Hard Wrap", WindowStyle::Default, + WindowBackend::Default, 32, {}, 1, false, true ), + UIApplication::Settings( Sys::getProcessPath() + ".." + FileSystem::getOSSlash(), 1 ) ); + FileSystem::changeWorkingDirectory( Sys::getProcessPath() ); + + auto colors = SyntaxColorScheme::getDefault(); + auto syntax = SyntaxDefinitionManager::instance()->getByLanguageName( "Markdown" ); + + app.getWindow()->setClearColor( colors.getEditorColor( "background"_sst ).toRGB() ); + app.getWindow()->clear(); + + Text text; + text.setFont( app.getUI()->getUIThemeManager()->getDefaultFont() ); + text.setFontSize( 16 ); + text.setColor( Color::Black ); + text.setString( string ); + text.hardWrapText( app.getWindow()->getSize().getWidth() ); + SyntaxTokenizer::tokenizeText( syntax, colors, &text ); + text.draw( 0, 0 ); + + compareImages( utest_state, utest_result, app.getWindow(), "eepp-text-hard-wrap" ); + }; + + UTEST_PRINT_STEP( "Text Shaper disabled" ); + { + BoolScopedOp op( Text::TextShaperEnabled, false ); + runTest(); + } + + UTEST_PRINT_STEP( "Text Shaper enabled" ); + { + BoolScopedOp op( Text::TextShaperEnabled, true ); + runTest(); + + UTEST_PRINT_STEP( "Text Shaper enabled w/o optimizations" ); + BoolScopedOp op2( Text::TextShaperOptimizations, false ); + runTest(); + } +} diff --git a/src/tests/unit_tests/utest.hpp b/src/tests/unit_tests/utest.hpp index 0915ece1f..b5f5e146d 100644 --- a/src/tests/unit_tests/utest.hpp +++ b/src/tests/unit_tests/utest.hpp @@ -1,55 +1,95 @@ #pragma once #include "utest.h" -#define UTEST_STDSTREQ(x, y, msg, is_assert) \ - UTEST_SURPRESS_WARNING_BEGIN do { \ - const std::string xEval = (x); \ - const std::string yEval = (y); \ - if (xEval != yEval) { \ - UTEST_PRINTF("%s:%i: Failure\n", __FILE__, __LINE__); \ - UTEST_PRINTF(" Expected : \"%s\"\n", xEval.c_str()); \ - UTEST_PRINTF(" Actual : \"%s\"\n", yEval.c_str()); \ - if (strlen(msg) > 0) { \ - UTEST_PRINTF(" Message : %s\n", msg); \ - } \ - *utest_result = UTEST_TEST_FAILURE; \ - if (is_assert) { \ - return; \ - } \ - } \ - } \ - while (0) \ - UTEST_SURPRESS_WARNING_END +#include +#include -#define EXPECT_STDSTREQ(x, y) UTEST_STDSTREQ(x, y, "", 0) -#define EXPECT_STDSTREQ_MSG(x, y, msg) UTEST_STDSTREQ(x, y, msg, 0) -#define ASSERT_STDSTREQ(x, y) UTEST_STDSTREQ(x, y, "", 1) -#define ASSERT_STDSTREQ_MSG(x, y, msg) UTEST_STDSTREQ(x, y, msg, 1) +template std::string vectorToString( const std::vector& vec ) { + std::ostringstream oss; + oss << "["; + bool first = true; + for ( const auto& element : vec ) { + if ( !first ) + oss << ", "; + oss << element; + first = false; + } + oss << "]"; + return oss.str(); +} -#define UTEST_STRINGEQ(x, y, msg, is_assert) \ - UTEST_SURPRESS_WARNING_BEGIN do { \ - const String xEval = (x); \ - const String yEval = (y); \ - if (xEval != yEval) { \ - UTEST_PRINTF("%s:%i: Failure\n", __FILE__, __LINE__); \ - UTEST_PRINTF(" Expected : \"%s\"\n", xEval.toUtf8().c_str()); \ - UTEST_PRINTF(" Actual : \"%s\"\n", yEval.toUtf8().c_str()); \ - if (strlen(msg) > 0) { \ - UTEST_PRINTF(" Message : %s\n", msg); \ - } \ - *utest_result = UTEST_TEST_FAILURE; \ - if (is_assert) { \ - return; \ - } \ - } \ - } \ - while (0) \ - UTEST_SURPRESS_WARNING_END +#define UTEST_STDSTREQ( x, y, msg, is_assert ) \ + UTEST_SURPRESS_WARNING_BEGIN do { \ + const std::string xEval = ( x ); \ + const std::string yEval = ( y ); \ + if ( xEval != yEval ) { \ + UTEST_PRINTF( "%s:%i: Failure\n", __FILE__, __LINE__ ); \ + UTEST_PRINTF( " Expected : \"%s\"\n", xEval.c_str() ); \ + UTEST_PRINTF( " Actual : \"%s\"\n", yEval.c_str() ); \ + if ( strlen( msg ) > 0 ) { \ + UTEST_PRINTF( " Message : %s\n", msg ); \ + } \ + *utest_result = UTEST_TEST_FAILURE; \ + if ( is_assert ) { \ + return; \ + } \ + } \ + } \ + while ( 0 ) \ + UTEST_SURPRESS_WARNING_END -#define EXPECT_STRINGEQ(x, y) UTEST_STRINGEQ(x, y, "", 0) -#define EXPECT_STRINGEQ_MSG(x, y, msg) UTEST_STRINGEQ(x, y, msg, 0) -#define ASSERT_STRINGEQ(x, y) UTEST_STRINGEQ(x, y, "", 1) -#define ASSERT_STRINGEQ_MSG(x, y, msg) UTEST_STRINGEQ(x, y, msg, 1) +#define EXPECT_STDSTREQ( x, y ) UTEST_STDSTREQ( x, y, "", 0 ) +#define EXPECT_STDSTREQ_MSG( x, y, msg ) UTEST_STDSTREQ( x, y, msg, 0 ) +#define ASSERT_STDSTREQ( x, y ) UTEST_STDSTREQ( x, y, "", 1 ) +#define ASSERT_STDSTREQ_MSG( x, y, msg ) UTEST_STDSTREQ( x, y, msg, 1 ) -#define UTEST_PRINT_INFO(str) UTEST_PRINTF( "\033[32m[ INFO ]\033[0m \t%s\n", str ) -#define UTEST_PRINT_STEP(str) UTEST_PRINTF( "\033[32m[ SUB-STEP ]\033[0m \t%s\n", str ) +#define UTEST_STRINGEQ( x, y, msg, is_assert ) \ + UTEST_SURPRESS_WARNING_BEGIN do { \ + const String xEval = ( x ); \ + const String yEval = ( y ); \ + if ( xEval != yEval ) { \ + UTEST_PRINTF( "%s:%i: Failure\n", __FILE__, __LINE__ ); \ + UTEST_PRINTF( " Expected : \"%s\"\n", xEval.toUtf8().c_str() ); \ + UTEST_PRINTF( " Actual : \"%s\"\n", yEval.toUtf8().c_str() ); \ + if ( strlen( msg ) > 0 ) { \ + UTEST_PRINTF( " Message : %s\n", msg ); \ + } \ + *utest_result = UTEST_TEST_FAILURE; \ + if ( is_assert ) { \ + return; \ + } \ + } \ + } \ + while ( 0 ) \ + UTEST_SURPRESS_WARNING_END + +#define EXPECT_STRINGEQ( x, y ) UTEST_STRINGEQ( x, y, "", 0 ) +#define EXPECT_STRINGEQ_MSG( x, y, msg ) UTEST_STRINGEQ( x, y, msg, 0 ) +#define ASSERT_STRINGEQ( x, y ) UTEST_STRINGEQ( x, y, "", 1 ) +#define ASSERT_STRINGEQ_MSG( x, y, msg ) UTEST_STRINGEQ( x, y, msg, 1 ) + +#define UTEST_PRINT_INFO( str ) UTEST_PRINTF( "\033[32m[ INFO ]\033[0m \t%s\n", str ) +#define UTEST_PRINT_STEP( str ) UTEST_PRINTF( "\033[32m[ SUB-STEP ]\033[0m \t%s\n", str ) + +#define UTEST_VECTOREQ( xEval, yEval, msg, is_assert ) \ + UTEST_SURPRESS_WARNING_BEGIN do { \ + if ( xEval != yEval ) { \ + UTEST_PRINTF( "%s:%i: Failure\n", __FILE__, __LINE__ ); \ + UTEST_PRINTF( " Expected : \"%s\"\n", vectorToString( xEval ).c_str() ); \ + UTEST_PRINTF( " Actual : \"%s\"\n", vectorToString( yEval ).c_str() ); \ + if ( strlen( msg ) > 0 ) { \ + UTEST_PRINTF( " Message : %s\n", msg ); \ + } \ + *utest_result = UTEST_TEST_FAILURE; \ + if ( is_assert ) { \ + return; \ + } \ + } \ + } \ + while ( 0 ) \ + UTEST_SURPRESS_WARNING_END + +#define EXPECT_VECTOREQ( x, y ) UTEST_VECTOREQ( x, y, "", 0 ) +#define EXPECT_VECTOREQ_MSG( x, y, msg ) UTEST_VECTOREQ( x, y, msg, 0 ) +#define ASSERT_VECTOREQ( x, y ) UTEST_VECTOREQ( x, y, "", 1 ) +#define ASSERT_VECTOREQ_MSG( x, y, msg ) UTEST_VECTOREQ( x, y, msg, 1 )