From db556fc38867516265568ee045b1462ea7b838eb Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Tue, 14 Jul 2020 23:36:23 +0200 Subject: [PATCH 1/8] Remove CppUnit boilerplate from wxDC::GetTextExtent() unit tests Drop the test case class and use CATCH macros. --- tests/graphics/measuring.cpp | 85 ++++++++++-------------------------- 1 file changed, 23 insertions(+), 62 deletions(-) diff --git a/tests/graphics/measuring.cpp b/tests/graphics/measuring.cpp index 397f586421..723ec9778a 100644 --- a/tests/graphics/measuring.cpp +++ b/tests/graphics/measuring.cpp @@ -33,45 +33,6 @@ #include "wx/dcps.h" #include "wx/metafile.h" -// ---------------------------------------------------------------------------- -// test class -// ---------------------------------------------------------------------------- - -class MeasuringTextTestCase : public CppUnit::TestCase -{ -public: - MeasuringTextTestCase() { } - -private: - CPPUNIT_TEST_SUITE( MeasuringTextTestCase ); - CPPUNIT_TEST( DCGetTextExtent ); - CPPUNIT_TEST( LeadingAndDescent ); - CPPUNIT_TEST( WindowGetTextExtent ); - CPPUNIT_TEST( GetPartialTextExtent ); -#ifdef TEST_GC - CPPUNIT_TEST( GraphicsGetTextExtent ); -#endif // TEST_GC - CPPUNIT_TEST_SUITE_END(); - - void DCGetTextExtent(); - void LeadingAndDescent(); - void WindowGetTextExtent(); - - void GetPartialTextExtent(); - -#ifdef TEST_GC - void GraphicsGetTextExtent(); -#endif // TEST_GC - - wxDECLARE_NO_COPY_CLASS(MeasuringTextTestCase); -}; - -// register in the unnamed registry so that these tests are run by default -CPPUNIT_TEST_SUITE_REGISTRATION( MeasuringTextTestCase ); - -// also include in its own registry so that these tests can be run alone -CPPUNIT_TEST_SUITE_NAMED_REGISTRATION( MeasuringTextTestCase, "MeasuringTextTestCase" ); - // ---------------------------------------------------------------------------- // helper for XXXTextExtent() methods // ---------------------------------------------------------------------------- @@ -86,11 +47,11 @@ struct GetTextExtentTester int y; obj.GetTextExtent("H", NULL, &y); - CPPUNIT_ASSERT( y > 1 ); + CHECK( y > 1 ); wxSize size = obj.GetTextExtent("Hello"); - CPPUNIT_ASSERT( size.x > 1 ); - CPPUNIT_ASSERT_EQUAL( y, size.y ); + CHECK( size.x > 1 ); + CHECK( size.y == y ); } }; @@ -98,7 +59,7 @@ struct GetTextExtentTester // tests themselves // ---------------------------------------------------------------------------- -void MeasuringTextTestCase::DCGetTextExtent() +TEST_CASE("wxDC::GetTextExtent", "[dc][text-extent]") { wxClientDC dc(wxTheApp->GetTopWindow()); @@ -107,9 +68,9 @@ void MeasuringTextTestCase::DCGetTextExtent() int w; dc.GetMultiLineTextExtent("Good\nbye", &w, NULL); const wxSize sz = dc.GetTextExtent("Good"); - CPPUNIT_ASSERT_EQUAL( sz.x, w ); + CHECK( w == sz.x ); - CPPUNIT_ASSERT( dc.GetMultiLineTextExtent("Good\nbye").y >= 2*sz.y ); + CHECK( dc.GetMultiLineTextExtent("Good\nbye").y >= 2*sz.y ); // Test the functions with some other DC kinds also. #if wxUSE_PRINTING_ARCHITECTURE && wxUSE_POSTSCRIPT @@ -128,19 +89,19 @@ void MeasuringTextTestCase::DCGetTextExtent() #endif } -void MeasuringTextTestCase::LeadingAndDescent() +TEST_CASE("wxDC::LeadingAndDescent", "[dc][text-extent]") { wxClientDC dc(wxTheApp->GetTopWindow()); // Retrieving just the descent should work. int descent = -17; dc.GetTextExtent("foo", NULL, NULL, &descent); - CPPUNIT_ASSERT( descent != -17 ); + CHECK( descent != -17 ); // Same for external leading. int leading = -289; dc.GetTextExtent("foo", NULL, NULL, NULL, &leading); - CPPUNIT_ASSERT( leading != -289 ); + CHECK( leading != -289 ); // And both should also work for the empty string as they retrieve the // values valid for the entire font and not just this string. @@ -148,46 +109,46 @@ void MeasuringTextTestCase::LeadingAndDescent() leading2; dc.GetTextExtent("", NULL, NULL, &descent2, &leading2); - CPPUNIT_ASSERT_EQUAL( descent, descent2 ); - CPPUNIT_ASSERT_EQUAL( leading, leading2 ); + CHECK( descent2 == descent ); + CHECK( leading2 == leading ); } -void MeasuringTextTestCase::WindowGetTextExtent() +TEST_CASE("wxWindow::GetTextExtent", "[window][text-extent]") { wxWindow* const win = wxTheApp->GetTopWindow(); GetTextExtentTester testWin(*win); } -void MeasuringTextTestCase::GetPartialTextExtent() +TEST_CASE("wxDC::GetPartialTextExtent", "[dc][text-extent][partial]") { wxClientDC dc(wxTheApp->GetTopWindow()); wxArrayInt widths; - CPPUNIT_ASSERT( dc.GetPartialTextExtents("Hello", widths) ); - CPPUNIT_ASSERT_EQUAL( 5, widths.size() ); - CPPUNIT_ASSERT_EQUAL( widths[0], dc.GetTextExtent("H").x ); - CPPUNIT_ASSERT_EQUAL( widths[4], dc.GetTextExtent("Hello").x ); + REQUIRE( dc.GetPartialTextExtents("Hello", widths) ); + REQUIRE( widths.size() == 5 ); + CHECK( widths[0] == dc.GetTextExtent("H").x ); + CHECK( widths[4] == dc.GetTextExtent("Hello").x ); } #ifdef TEST_GC -void MeasuringTextTestCase::GraphicsGetTextExtent() +TEST_CASE("wxGC::GetTextExtent", "[dc][text-extent]") { wxGraphicsRenderer* renderer = wxGraphicsRenderer::GetDefaultRenderer(); - CPPUNIT_ASSERT(renderer); + REQUIRE(renderer); wxGraphicsContext* context = renderer->CreateMeasuringContext(); - CPPUNIT_ASSERT(context); + REQUIRE(context); wxFont font(12, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL); - CPPUNIT_ASSERT(font.IsOk()); + REQUIRE(font.IsOk()); context->SetFont(font, *wxBLACK); double width, height, descent, externalLeading = 0.0; context->GetTextExtent("x", &width, &height, &descent, &externalLeading); delete context; // TODO: Determine a way to make these tests more robust. - CPPUNIT_ASSERT(width > 0.0); - CPPUNIT_ASSERT(height > 0.0); + CHECK(width > 0.0); + CHECK(height > 0.0); } From 2280e43fe9544c214ebc7e3740e3280b579451fb Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Wed, 15 Jul 2020 01:16:23 +0200 Subject: [PATCH 2/8] Factor out wxTextMeasureBase::GetEmptyLineHeight() No real changes, just refactor to extract a trivial helper function. --- include/wx/private/textmeasure.h | 4 ++++ src/common/textmeasurecmn.cpp | 14 +++++++++----- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/include/wx/private/textmeasure.h b/include/wx/private/textmeasure.h index db96dcd38f..3671a87180 100644 --- a/include/wx/private/textmeasure.h +++ b/include/wx/private/textmeasure.h @@ -128,6 +128,10 @@ protected: wxCoord *descent = NULL, wxCoord *externalLeading = NULL); + // Get line height: used when the line is empty because CallGetTextExtent() + // would just return (0, 0) in this case. + int GetEmptyLineHeight(); + // Return a valid font: if one was given to us in the ctor, use this one, // otherwise use the current font of the associated wxDC or wxWindow. wxFont GetFont() const; diff --git a/src/common/textmeasurecmn.cpp b/src/common/textmeasurecmn.cpp index 16448d2664..b54bebeb6a 100644 --- a/src/common/textmeasurecmn.cpp +++ b/src/common/textmeasurecmn.cpp @@ -100,6 +100,13 @@ void wxTextMeasureBase::GetTextExtent(const wxString& string, CallGetTextExtent(string, width, height, descent, externalLeading); } +int wxTextMeasureBase::GetEmptyLineHeight() +{ + int dummy, height; + CallGetTextExtent(wxS("W"), &dummy, &height); + return height; +} + void wxTextMeasureBase::GetMultiLineTextExtent(const wxString& text, wxCoord *width, wxCoord *height, @@ -136,12 +143,9 @@ void wxTextMeasureBase::GetMultiLineTextExtent(const wxString& text, if ( !heightLineDefault ) heightLineDefault = heightLine; + // and if we hadn't had any previous one neither, compute it now if ( !heightLineDefault ) - { - // but we don't know it yet - choose something reasonable - int dummy; - CallGetTextExtent(wxS("W"), &dummy, &heightLineDefault); - } + heightLineDefault = GetEmptyLineHeight(); heightTextTotal += heightLineDefault; } From 55c71fdac954094b831d6424464f6a9126839863 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Wed, 15 Jul 2020 01:17:20 +0200 Subject: [PATCH 3/8] Ensure that pointers in GetMultiLineTextExtent() are always valid Do the same thing as in GetTextExtent() to simplify the code a little and also avoid temporary variables. No real changes. --- src/common/textmeasurecmn.cpp | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/src/common/textmeasurecmn.cpp b/src/common/textmeasurecmn.cpp index b54bebeb6a..f553f7f474 100644 --- a/src/common/textmeasurecmn.cpp +++ b/src/common/textmeasurecmn.cpp @@ -112,20 +112,30 @@ void wxTextMeasureBase::GetMultiLineTextExtent(const wxString& text, wxCoord *height, wxCoord *heightOneLine) { + // To make the code simpler, make sure that the width and height pointers + // are always valid, by making them point to dummy variables if necessary. + int unusedWidth, unusedHeight; + if ( !width ) + width = &unusedWidth; + if ( !height ) + height = &unusedHeight; + + *width = 0; + *height = 0; + // It's noticeably faster to handle the case of a string which isn't // actually multiline specially here, to skip iteration above in this case. if ( text.find('\n') == wxString::npos ) { GetTextExtent(text, width, height); - if ( heightOneLine && height ) + if ( heightOneLine ) *heightOneLine = *height; return; } MeasuringGuard guard(*this); - wxCoord widthTextMax = 0, widthLine, - heightTextTotal = 0, heightLineDefault = 0, heightLine = 0; + wxCoord widthLine, heightLine = 0, heightLineDefault = 0; wxString::const_iterator lineStart = text.begin(); for ( wxString::const_iterator pc = text.begin(); ; ++pc ) @@ -147,14 +157,14 @@ void wxTextMeasureBase::GetMultiLineTextExtent(const wxString& text, if ( !heightLineDefault ) heightLineDefault = GetEmptyLineHeight(); - heightTextTotal += heightLineDefault; + *height += heightLineDefault; } else { CallGetTextExtent(wxString(lineStart, pc), &widthLine, &heightLine); - if ( widthLine > widthTextMax ) - widthTextMax = widthLine; - heightTextTotal += heightLine; + if ( widthLine > *width ) + *width = widthLine; + *height += heightLine; } if ( pc == text.end() ) @@ -169,10 +179,6 @@ void wxTextMeasureBase::GetMultiLineTextExtent(const wxString& text, } } - if ( width ) - *width = widthTextMax; - if ( height ) - *height = heightTextTotal; if ( heightOneLine ) *heightOneLine = heightLine; } From 844ec191f0e828f7a166bc15cec315cd7f3b9572 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Wed, 15 Jul 2020 01:21:59 +0200 Subject: [PATCH 4/8] Use CallGetTextExtent() directly in GetMultiLineTextExtent() There is no need to pass by GetTextExtent() now that we already make sure width/height are non-null in this function, so just construct the MeasuringGuard slightly earlier and call CallGetTextExtent() directly. No real changes. --- src/common/textmeasurecmn.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/common/textmeasurecmn.cpp b/src/common/textmeasurecmn.cpp index f553f7f474..e060fb18b5 100644 --- a/src/common/textmeasurecmn.cpp +++ b/src/common/textmeasurecmn.cpp @@ -123,18 +123,18 @@ void wxTextMeasureBase::GetMultiLineTextExtent(const wxString& text, *width = 0; *height = 0; + MeasuringGuard guard(*this); + // It's noticeably faster to handle the case of a string which isn't // actually multiline specially here, to skip iteration above in this case. if ( text.find('\n') == wxString::npos ) { - GetTextExtent(text, width, height); + CallGetTextExtent(text, width, height); if ( heightOneLine ) *heightOneLine = *height; return; } - MeasuringGuard guard(*this); - wxCoord widthLine, heightLine = 0, heightLineDefault = 0; wxString::const_iterator lineStart = text.begin(); From a52a27ad9015e7f531c6adbeaba2f923c88beb67 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Wed, 15 Jul 2020 01:58:07 +0200 Subject: [PATCH 5/8] Display wxSize less confusingly if any tests fail Using "x" as separator between the components created confusion with hexadecimal notation, resulting in confusing messages like "0x17==0x0" that seemed to describe comparison of 2 numbers and not 2 wxSize objects. Using "*" is also more consistent with wxRect output format. --- tests/asserthelper.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/asserthelper.cpp b/tests/asserthelper.cpp index 7b25cec2d6..73a980cac1 100644 --- a/tests/asserthelper.cpp +++ b/tests/asserthelper.cpp @@ -49,7 +49,7 @@ std::ostream& operator<<(std::ostream& os, const wxColour& c) std::ostream& operator<<(std::ostream& os, const wxSize& s) { - os << s.x << "x" << s.y; + os << s.x << "*" << s.y; return os; } From 46d6866c9f6235a75c894e4d49db4e7e82afad09 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Wed, 15 Jul 2020 02:01:36 +0200 Subject: [PATCH 6/8] Make wxGCDC::GetTextExtent("") return (0, 0) This seems more logical and is compatible with wxDC in wxMSW and wxGTK2, as well as other kinds of DC, e.g. wxPostScriptDC. It also looks like the current behaviour was unintentional as it happened only because wxGCDCImpl::DoGetTextExtent() always passed all non-null parameters to wxGraphicsContext::GetTextExtent(), even if it didn't need the values for all of them, and thus bypassed the special case for the empty string which was already present in the latter function. Fix this, making DoGetTextExtent() more efficient as a side effect (we now avoid unnecessary calls to pango_layout_iter_get_baseline() in the most common case), and also add another test for empty string to wxGraphicsContext itself, for non-GTK case. Also document this behaviour and add a test checking for it. --- docs/changes.txt | 3 +++ interface/wx/dc.h | 2 ++ src/common/dcgraph.cpp | 11 ++++++++++- src/generic/graphicc.cpp | 2 +- tests/graphics/measuring.cpp | 5 +++++ 5 files changed, 21 insertions(+), 2 deletions(-) diff --git a/docs/changes.txt b/docs/changes.txt index 156e0bf467..e590145773 100644 --- a/docs/changes.txt +++ b/docs/changes.txt @@ -68,6 +68,9 @@ Changes in behaviour not resulting in compilation errors - wxGTK wxTextCtrl doesn't generate any wxEVT_TEXT when it's created with non-empty value, for consistency with the other ports. +- wxDC::GetTextExtent() returns height of 0 for empty string in wxGTK and wxOSX + too now, for consistency with wxMSW and other kinds of wxDC. + - wxMSW wxToolBar height now adapts to the height of embedded controls, making the toolbar taller if necessary, rather than making the controls smaller. To return to the previous behaviour, you need to explicitly create controls of diff --git a/interface/wx/dc.h b/interface/wx/dc.h index 9a2cbe21da..114b9cc951 100644 --- a/interface/wx/dc.h +++ b/interface/wx/dc.h @@ -954,6 +954,8 @@ public: used for the text extent calculation. Otherwise the currently selected font is. + If @a string is empty, its extent is 0 in both directions, as expected. + @note This function only works with single-line strings. @beginWxPerlOnly diff --git a/src/common/dcgraph.cpp b/src/common/dcgraph.cpp index fa5a7558be..f519943100 100644 --- a/src/common/dcgraph.cpp +++ b/src/common/dcgraph.cpp @@ -1236,7 +1236,16 @@ void wxGCDCImpl::DoGetTextExtent( const wxString &str, wxCoord *width, wxCoord * wxDouble h , d , e , w; - m_graphicContext->GetTextExtent( str, &w, &h, &d, &e ); + // Don't pass non-NULL pointers for the parts we don't need, this could + // result in doing extra unnecessary work inside GetTextExtent(). + m_graphicContext->GetTextExtent + ( + str, + width ? &w : NULL, + height ? &h : NULL, + descent ? &d : NULL, + externalLeading ? &e : NULL + ); if ( height ) *height = (wxCoord)(h+0.5); diff --git a/src/generic/graphicc.cpp b/src/generic/graphicc.cpp index b98a62ecf7..0eb6646099 100644 --- a/src/generic/graphicc.cpp +++ b/src/generic/graphicc.cpp @@ -2866,7 +2866,7 @@ void wxCairoContext::GetTextExtent( const wxString &str, wxDouble *width, wxDoub fe.height = fe.ascent + fe.descent; } - if (height) + if (height && !str.empty()) *height = fe.height; if ( descent ) *descent = fe.descent; diff --git a/tests/graphics/measuring.cpp b/tests/graphics/measuring.cpp index 723ec9778a..ad654cfe96 100644 --- a/tests/graphics/measuring.cpp +++ b/tests/graphics/measuring.cpp @@ -33,6 +33,8 @@ #include "wx/dcps.h" #include "wx/metafile.h" +#include "asserthelper.h" + // ---------------------------------------------------------------------------- // helper for XXXTextExtent() methods // ---------------------------------------------------------------------------- @@ -52,6 +54,9 @@ struct GetTextExtentTester wxSize size = obj.GetTextExtent("Hello"); CHECK( size.x > 1 ); CHECK( size.y == y ); + + // Test that getting text extent of an empty string returns (0, 0). + CHECK( obj.GetTextExtent(wxString()) == wxSize() ); } }; From d57c688d8928e3fb8144e5bb0a01efa2aa36ca38 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Wed, 15 Jul 2020 01:23:29 +0200 Subject: [PATCH 7/8] Return non-zero height from GetMultiLineTextExtent("") in wxMSW This restores the previous behaviour inadvertently changed by bfeae1922d (Minor optimizations in GetMultiLineTextExtent(), 2020-06-10) and makes it official by documenting it and adding tests checking for it. It wasn't completely obviously if this was intentional or accidental before, but at least wxStaticText itself relied on the old behaviour, and chances are that so did some code outside the library, so make this part of the API now. See #18825. --- interface/wx/dc.h | 6 ++++++ src/common/textmeasurecmn.cpp | 8 +++++++- tests/graphics/measuring.cpp | 6 ++++++ 3 files changed, 19 insertions(+), 1 deletion(-) diff --git a/interface/wx/dc.h b/interface/wx/dc.h index 114b9cc951..527d97eb64 100644 --- a/interface/wx/dc.h +++ b/interface/wx/dc.h @@ -892,6 +892,12 @@ public: used for the text extent calculation, otherwise the currently selected font is used. + If @a string is empty, its horizontal extent is 0 but, for convenience + when using this function for allocating enough space for a possibly + multi-line string, its vertical extent is the same as the height of an + empty line of text. Please note that this behaviour differs from that + of GetTextExtent(). + @note This function works with both single-line and multi-line strings. @beginWxPerlOnly diff --git a/src/common/textmeasurecmn.cpp b/src/common/textmeasurecmn.cpp index e060fb18b5..8ad8e0a312 100644 --- a/src/common/textmeasurecmn.cpp +++ b/src/common/textmeasurecmn.cpp @@ -129,7 +129,13 @@ void wxTextMeasureBase::GetMultiLineTextExtent(const wxString& text, // actually multiline specially here, to skip iteration above in this case. if ( text.find('\n') == wxString::npos ) { - CallGetTextExtent(text, width, height); + // This case needs to be handled specially as we're supposed to return + // a non-zero height even for empty string. + if ( text.empty() ) + *height = GetEmptyLineHeight(); + else + CallGetTextExtent(text, width, height); + if ( heightOneLine ) *heightOneLine = *height; return; diff --git a/tests/graphics/measuring.cpp b/tests/graphics/measuring.cpp index ad654cfe96..bb185247af 100644 --- a/tests/graphics/measuring.cpp +++ b/tests/graphics/measuring.cpp @@ -77,6 +77,12 @@ TEST_CASE("wxDC::GetTextExtent", "[dc][text-extent]") CHECK( dc.GetMultiLineTextExtent("Good\nbye").y >= 2*sz.y ); + // Check that empty lines get counted + CHECK( dc.GetMultiLineTextExtent("\n\n\n").y >= 3*sz.y ); + + // And even empty strings count like one line. + CHECK( dc.GetMultiLineTextExtent(wxString()) == wxSize(0, sz.y) ); + // Test the functions with some other DC kinds also. #if wxUSE_PRINTING_ARCHITECTURE && wxUSE_POSTSCRIPT wxPostScriptDC psdc; From 99f210a0e7041b7aba1816cd0a0643a8eb762d6b Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Thu, 16 Jul 2020 01:49:23 +0200 Subject: [PATCH 8/8] Avoid uninitialized variable warnings in debug MSVC build The compiler is smart enough to determine that the variables are always initialized when they're actually used when using optimizations, but gives a bunch of C4701 warnings for them if we don't initialize them in the non-optimized debug builds. --- src/common/dcgraph.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/common/dcgraph.cpp b/src/common/dcgraph.cpp index f519943100..5da2fe531d 100644 --- a/src/common/dcgraph.cpp +++ b/src/common/dcgraph.cpp @@ -1234,7 +1234,10 @@ void wxGCDCImpl::DoGetTextExtent( const wxString &str, wxCoord *width, wxCoord * m_graphicContext->SetFont( *theFont, m_textForegroundColour ); } - wxDouble h , d , e , w; + wxDouble w wxDUMMY_INITIALIZE(0), + h wxDUMMY_INITIALIZE(0), + d wxDUMMY_INITIALIZE(0), + e wxDUMMY_INITIALIZE(0); // Don't pass non-NULL pointers for the parts we don't need, this could // result in doing extra unnecessary work inside GetTextExtent().