Index: ps/trunk/source/gui/CGUIText.cpp =================================================================== --- ps/trunk/source/gui/CGUIText.cpp +++ ps/trunk/source/gui/CGUIText.cpp @@ -204,12 +204,15 @@ // Append X value. x += feedback2.m_Size.Width; - if (width != 0 && x - spaceCorrection > widthRangeTo && j != tempFrom && !feedback2.m_NewLine) + const float currentSpaceCorrection = feedback2.m_EndsWithSpace ? spaceWidth : 0.0f; + + const bool isLineOverflow = x - currentSpaceCorrection > widthRangeTo; + if (width != 0 && isLineOverflow && j != tempFrom && !feedback2.m_NewLine) break; // Update after the line-break detection, because otherwise spaceCorrection above // will refer to the wrapped word and not the last-word-before-the-line-break. - spaceCorrection = feedback2.m_EndsWithSpace ? spaceWidth : 0.f; + spaceCorrection = currentSpaceCorrection; // Let lineSize.cy be the maximum m_Height we encounter. lineSize.Height = std::max(lineSize.Height, feedback2.m_Size.Height); Index: ps/trunk/source/gui/tests/test_CGUIText.h =================================================================== --- ps/trunk/source/gui/tests/test_CGUIText.h +++ ps/trunk/source/gui/tests/test_CGUIText.h @@ -18,6 +18,7 @@ #include "lib/self_test.h" #include "graphics/FontManager.h" +#include "graphics/FontMetrics.h" #include "gui/CGUI.h" #include "gui/CGUIText.h" #include "gui/SettingTypes/CGUIString.h" @@ -29,10 +30,14 @@ #include "renderer/Renderer.h" #include "scriptinterface/ScriptInterface.h" +#include +#include + class TestCGUIText : public CxxTest::TestSuite { CProfileViewer* m_Viewer = nullptr; CRenderer* m_Renderer = nullptr; + public: void setUp() { @@ -63,7 +68,7 @@ CConfigDB::Shutdown(); CXeromyces::Terminate(); g_VFS.reset(); - DeleteDirectory(DataDir()/"_testcache"); + DeleteDirectory(DataDir() / "_testcache"); } void test_empty() @@ -76,11 +81,11 @@ { CGUI gui(g_ScriptContext); - static CStrW font = L"console"; + const CStrW font = L"console"; // Make sure this matches the value of the file. // TODO: load dynamically. - static const float lineHeight = 12.f; - static const float lineSpacing = 15.f; + const float lineHeight = 12.f; + const float lineSpacing = 15.f; CGUIString string; CGUIText text; @@ -155,15 +160,119 @@ TS_ASSERT_EQUALS(text.GetSize().Height, padding * 2 + lineHeight + lineSpacing * 2); } + void test_layout_wrapping_center() + { + // The short word should be layouted the same as when there is no + // second word, because it's the only one word in the first line until + // the width is enough to fit both. So we need to check that for + // increasing width X positions of both words are also increasing until + // they fit into a single line. + // + // Width is too small for both: + // +--------------------+ + // | Shortword | + // | (Veryverylongword) | + // +--------------------+ + // + // Right before the big enough width: + // +---------------------------+ + // | Shortword | + // | (Veryverylongword) | + // +---------------------------+ + // + // Width is enough to fit both: + // +------------------------------+ + // | Shortword (Veryverylongword) | + // +------------------------------+ + // + + CGUI gui(g_ScriptContext); + const CStrW firstWord = L"Shortword"; + const CStrW secondWord = L"(Veryverylongword)"; + const CStrW text = firstWord + L" " + secondWord; + CGUIString string; + string.SetValue(text); + const CStrW font = L"console"; + CFontMetrics fontMetrics{CStrIntern(font.ToUTF8())}; + + int firstWordWidth = 0, firstWordHeight = 0; + fontMetrics.CalculateStringSize(firstWord.c_str(), firstWordWidth, firstWordHeight); + TS_ASSERT(firstWordWidth > 0); + int secondWordWidth = 0, secondWordHeight = 0; + fontMetrics.CalculateStringSize(secondWord.c_str(), secondWordWidth, secondWordHeight); + TS_ASSERT(secondWordWidth > 0); + TS_ASSERT(firstWordWidth < secondWordWidth); + const float spaceWidth = fontMetrics.GetCharacterWidth(L' '); + + const float lineHeight = fontMetrics.GetHeight(); + const float lineSpacing = fontMetrics.GetLineSpacing(); + + auto layoutText = [&gui, &string, &font, lineHeight, firstWordWidth](const float width) + { + const float padding = 0.0f; + const CGUIText text(gui, string, font, width, padding, EAlign::CENTER, nullptr); + + std::array positions; + TS_ASSERT_EQUALS(text.GetTextCalls().size(), positions.size()); + + // If the second word is on the next line then the first word should be + // centered alone. + if (text.GetTextCalls()[0].m_Pos.Y + lineHeight <= text.GetTextCalls()[1].m_Pos.Y) + { + const float expectedX = (width - firstWordWidth) / 2.0f; + TS_ASSERT_EQUALS(text.GetTextCalls()[0].m_Pos.X, expectedX); + } + + std::transform( + text.GetTextCalls().begin(), text.GetTextCalls().end(), positions.begin(), + [](const CGUIText::STextCall& textCall) { return textCall.m_Pos; }); + return positions; + }; + + const float testPadding = 2; + const float beginWidth = firstWordWidth - testPadding; + const float endWidth = firstWordWidth + spaceWidth + secondWordWidth + testPadding; + std::array previousPositions = layoutText(beginWidth); + TS_ASSERT_EQUALS(std::get<0>(previousPositions).Y, lineHeight); + TS_ASSERT_EQUALS(std::get<1>(previousPositions).Y, lineHeight + lineSpacing); + bool firstFit = false; + for (float width = beginWidth; width <= endWidth; width += 1.0f) + { + std::array positions = layoutText(width); + TS_ASSERT_EQUALS(std::get<0>(positions).Y, lineHeight); + if (std::get<0>(positions).X >= std::get<0>(previousPositions).X) + { + if (firstFit) + { + TS_ASSERT_EQUALS(std::get<0>(positions).Y, std::get<1>(positions).Y); + } + else + { + TS_ASSERT_EQUALS(std::get<1>(positions).Y, lineHeight + lineSpacing); + } + TS_ASSERT(std::get<1>(positions).X >= std::get<1>(previousPositions).X); + } + else + { + TS_ASSERT(!firstFit); + firstFit = true; + TS_ASSERT_EQUALS(std::get<0>(positions).Y, std::get<1>(positions).Y); + TS_ASSERT(std::get<0>(positions).X < std::get<1>(positions).X); + } + previousPositions = positions; + } + TS_ASSERT(firstFit); + } + void test_overflow() { CGUI gui(g_ScriptContext); - static CStrW font = L"console"; + const CStrW font = L"console"; // Make sure this matches the value of the file. // TODO: load dynamically. - static const float lineHeight = 12.f; - static const float lineSpacing = 15.f; + const float lineHeight = 12.f; + const float lineSpacing = 15.f; float renderedWidth = 0.f; const float width = 200.f; @@ -211,7 +320,7 @@ CGUI gui(g_ScriptContext); - static CStrW font = L"sans-bold-13"; + const CStrW font = L"sans-bold-13"; CGUIString string; CGUIText text; @@ -227,11 +336,11 @@ { CGUI gui(g_ScriptContext); - static CStrW font = L"console"; + const CStrW font = L"console"; // Make sure this matches the value of the file. // TODO: load dynamically. - static const float lineHeight = 12.f; - static const float lineSpacing = 15.f; + const float lineHeight = 12.f; + const float lineSpacing = 15.f; CGUIString string; CGUIText text;