From e0047bbf4fbff8198752164592f43bb58ed62e59 Mon Sep 17 00:00:00 2001 From: ANZ1217 Date: Fri, 29 Dec 2023 10:01:13 +0900 Subject: [PATCH] Improve Textfit performance We can assume point size of text and size of text's layout are directly proportional. So the size of the largest text point size can be calculated by O(1) operations. Previously, this was O(logn) operations. Change-Id: Ie5aae28e949738ce0f67e389f3e920663f4e19bc --- .../src/dali-toolkit/utc-Dali-TextLabel.cpp | 84 ++++++++++++ .../text/controller/text-controller-relayouter.cpp | 151 ++++++++++++++++----- 2 files changed, 201 insertions(+), 34 deletions(-) diff --git a/automated-tests/src/dali-toolkit/utc-Dali-TextLabel.cpp b/automated-tests/src/dali-toolkit/utc-Dali-TextLabel.cpp index cf57bc0..a45aa41 100644 --- a/automated-tests/src/dali-toolkit/utc-Dali-TextLabel.cpp +++ b/automated-tests/src/dali-toolkit/utc-Dali-TextLabel.cpp @@ -2089,6 +2089,90 @@ int UtcDaliToolkitTextlabelTextFitStressTest(void) END_TEST; } +int UtcDaliToolkitTextlabelFastTextFitStressTest(void) +{ + ToolkitTestApplication application; + tet_infoline(" UtcDaliToolkitTextlabelFastTextFitStressTest"); + TextLabel label = TextLabel::New(); + label.SetProperty(TextLabel::Property::TEXT, "Hello world"); + label.SetProperty(TextLabel::Property::POINT_SIZE, 120.f); + + // connect to the text git changed signal. + ConnectionTracker* testTracker = new ConnectionTracker(); + DevelTextLabel::TextFitChangedSignal(label).Connect(&TestTextFitChangedCallback); + bool textFitChangedSignal = false; + label.ConnectSignal(testTracker, "textFitChanged", CallbackFunctor(&textFitChangedSignal)); + gTextFitChangedCallBackCalled = false; + + application.GetScene().Add(label); + + // check text label width at range [100, 299] + for(int i=100; i<300; i++) + { + Vector2 size((float)i, 200.f); + label.SetProperty(Actor::Property::SIZE, size); + + // check point size with veryvery big range + Property::Map textFitMapSet; + textFitMapSet["enable"] = true; + textFitMapSet["minSize"] = 10.f; + textFitMapSet["maxSize"] = 10000.f; + textFitMapSet["stepSize"] = -1.0f; + textFitMapSet["fontSizeType"] = "pointSize"; + + label.SetProperty(Toolkit::DevelTextLabel::Property::TEXT_FIT, textFitMapSet); + + application.SendNotification(); + application.Render(); + } + + DALI_TEST_CHECK(gTextFitChangedCallBackCalled); + DALI_TEST_CHECK(textFitChangedSignal); + + END_TEST; +} + +int UtcDaliToolkitTextlabelTextFitMultiLine(void) +{ + ToolkitTestApplication application; + tet_infoline(" UtcDaliToolkitTextlabelTextFitMultiLine"); + TextLabel label = TextLabel::New(); + Vector2 size(500.0f, 100.0f); + label.SetProperty(Actor::Property::SIZE, size); + label.SetProperty(TextLabel::Property::TEXT, "Hello world\nHello world"); + label.SetProperty(TextLabel::Property::MULTI_LINE, true); + + // connect to the text git changed signal. + ConnectionTracker* testTracker = new ConnectionTracker(); + DevelTextLabel::TextFitChangedSignal(label).Connect(&TestTextFitChangedCallback); + bool textFitChangedSignal = false; + label.ConnectSignal(testTracker, "textFitChanged", CallbackFunctor(&textFitChangedSignal)); + gTextFitChangedCallBackCalled = false; + + Property::Map textFitMapSet; + textFitMapSet["enable"] = true; + textFitMapSet["minSize"] = 10.f; + textFitMapSet["maxSize"] = 100.f; + textFitMapSet["stepSize"] = -1.0f; + textFitMapSet["fontSizeType"] = "pointSize"; + + label.SetProperty(Toolkit::DevelTextLabel::Property::TEXT_FIT, textFitMapSet); + label.SetProperty(TextLabel::Property::POINT_SIZE, 120.f); + + application.GetScene().Add(label); + + application.SendNotification(); + application.Render(); + + float textFitFontSize = (label.GetProperty(Dali::Toolkit::DevelTextLabel::Property::TEXT_FIT).Get())["fontSize"].Get(); + DALI_TEST_EQUALS(textFitFontSize, 14.f, TEST_LOCATION); + + DALI_TEST_CHECK(gTextFitChangedCallBackCalled); + DALI_TEST_CHECK(textFitChangedSignal); + + END_TEST; +} + int UtcDaliToolkitTextlabelTextFitArray(void) { ToolkitTestApplication application; diff --git a/dali-toolkit/internal/text/controller/text-controller-relayouter.cpp b/dali-toolkit/internal/text/controller/text-controller-relayouter.cpp index bc3bb55..de4c776 100644 --- a/dali-toolkit/internal/text/controller/text-controller-relayouter.cpp +++ b/dali-toolkit/internal/text/controller/text-controller-relayouter.cpp @@ -19,8 +19,10 @@ #include // EXTERNAL INCLUDES +#include #include #include +#include #include #include @@ -379,61 +381,142 @@ void Controller::Relayouter::FitPointSizeforLayout(Controller& controller, const float pointInterval = impl.mTextFitStepSize; float currentFitPointSize = impl.mFontDefaults->mFitPointSize; float currentDefaultLineSize = impl.mLayoutEngine.GetDefaultLineSize(); + bool isMultiLine = impl.mLayoutEngine.GetLayout() == Layout::Engine::MULTI_LINE_BOX; // Instead of using the LineSize of the current TextLabel, the LineSize set in TextFit is used. + impl.SetDefaultLineSize(impl.mTextFitLineSize); model->mElideEnabled = false; + float bestPointSize = minPointSize; // check zero value if(pointInterval < 1.f) { impl.mTextFitStepSize = pointInterval = 1.0f; } - uint32_t pointSizeRange = static_cast(ceil((maxPointSize - minPointSize) / pointInterval)); - // Ensure minPointSize + pointSizeRange * pointInverval >= maxPointSize - while(minPointSize + static_cast(pointSizeRange) * pointInterval < maxPointSize) - { - ++pointSizeRange; - } + uint32_t pointSizeRange = static_cast(ceil((maxPointSize - minPointSize) / pointInterval)); - uint32_t bestSizeIndex = 0; - uint32_t minIndex = bestSizeIndex + 1u; - uint32_t maxIndex = pointSizeRange + 1u; - - bool bestSizeUpdatedLatest = false; - // Find best size as binary search. - // Range format as [l r). (left closed, right opened) - // It mean, we already check all i < l is valid, and r <= i is invalid. - // Below binary search will check m = (l+r)/2 point. - // Search area sperate as [l m) or [m+1 r) - // - // Basically, we can assume that 0 (minPointSize) is always valid. - // Now, we will check [1 pointSizeRange] range s.t. pointSizeRange mean the maxPointSize - while(minIndex < maxIndex) + if(isMultiLine || pointSizeRange < 3) { - uint32_t testIndex = minIndex + ((maxIndex - minIndex) >> 1u); - const float testPointSize = std::min(maxPointSize, minPointSize + static_cast(testIndex) * pointInterval); + // Ensure minPointSize + pointSizeRange * pointInverval >= maxPointSize + while(minPointSize + static_cast(pointSizeRange) * pointInterval < maxPointSize) + { + ++pointSizeRange; + } - if(CheckForTextFit(controller, testPointSize, layoutSize)) + uint32_t bestSizeIndex = 0; + uint32_t minIndex = bestSizeIndex + 1u; + uint32_t maxIndex = pointSizeRange + 1u; + + bool bestSizeUpdatedLatest = false; + // Find best size as binary search. + // Range format as [l r). (left closed, right opened) + // It mean, we already check all i < l is valid, and r <= i is invalid. + // Below binary search will check m = (l+r)/2 point. + // Search area sperate as [l m) or [m+1 r) + // + // Basically, we can assume that 0 (minPointSize) is always valid. + // Now, we will check [1 pointSizeRange] range s.t. pointSizeRange mean the maxPointSize + while(minIndex < maxIndex) { - bestSizeUpdatedLatest = true; + uint32_t testIndex = minIndex + ((maxIndex - minIndex) >> 1u); + const float testPointSize = std::min(maxPointSize, minPointSize + static_cast(testIndex) * pointInterval); + + if(CheckForTextFit(controller, testPointSize, layoutSize)) + { + bestSizeUpdatedLatest = true; - bestSizeIndex = testIndex; - minIndex = testIndex + 1u; + bestSizeIndex = testIndex; + minIndex = testIndex + 1u; + } + else + { + bestSizeUpdatedLatest = false; + maxIndex = testIndex; + } } - else + bestPointSize = std::min(maxPointSize, minPointSize + static_cast(bestSizeIndex) * pointInterval); + + // Best point size was not updated. re-run so the TextFit should be fitted really. + if(!bestSizeUpdatedLatest) { - bestSizeUpdatedLatest = false; - maxIndex = testIndex; + CheckForTextFit(controller, bestPointSize, layoutSize); } } - const float bestPointSize = std::min(maxPointSize, minPointSize + static_cast(bestSizeIndex) * pointInterval); - - // Best point size was not updated. re-run so the TextFit should be fitted really. - if(!bestSizeUpdatedLatest) + else { - CheckForTextFit(controller, bestPointSize, layoutSize); + // assume textSize = a * pointSize + b, finding a and b. + Size textSize; + TextUpdateInfo& textUpdateInfo = impl.mTextUpdateInfo; + const OperationsMask onlyOnceOperations = static_cast(CONVERT_TO_UTF32 | + GET_SCRIPTS | + VALIDATE_FONTS | + GET_LINE_BREAKS | + BIDI_INFO | + SHAPE_TEXT | + GET_GLYPH_METRICS); + + float resultBasedX[2]; + float resultBasedY[2]; + float tmpPointSize[2] = {minPointSize, maxPointSize}; + + // Calculate a and b by creating simultaneous equations with two calculations. + for(int i=0;i<2;i++) + { + impl.mFontDefaults->mFitPointSize = tmpPointSize[i]; + impl.mFontDefaults->sizeDefined = true; + impl.ClearFontData(); + + textUpdateInfo.mParagraphCharacterIndex = 0u; + textUpdateInfo.mRequestedNumberOfCharacters = impl.mModel->mLogicalModel->mText.Count(); + + + // Make sure the model is up-to-date before layouting + impl.UpdateModel(onlyOnceOperations); + + DoRelayout(impl, + Size(layoutSize.width, MAX_FLOAT), + static_cast(onlyOnceOperations | LAYOUT), + textSize); + + // Clear the update info. This info will be set the next time the text is updated. + textUpdateInfo.Clear(); + textUpdateInfo.mClearAll = true; + + resultBasedX[i] = textSize.x; + resultBasedY[i] = textSize.y; + } + + float aBasedX = (resultBasedX[1] - resultBasedX[0]) / (tmpPointSize[1] - tmpPointSize[0]); + float bBasedX = resultBasedX[1] - aBasedX * tmpPointSize[1]; + aBasedX = std::max(aBasedX, Dali::Math::MACHINE_EPSILON_1000); + + float aBasedY = (resultBasedY[1] - resultBasedY[0]) / (tmpPointSize[1] - tmpPointSize[0]); + float bBasedY = resultBasedY[1] - aBasedY * tmpPointSize[1]; + aBasedY = std::max(aBasedY, Dali::Math::MACHINE_EPSILON_1000); + + float bestPointSizeBasedX = (layoutSize.x - bBasedX) / aBasedX; + float bestPointSizeBasedY = (layoutSize.y - bBasedY) / aBasedY; + + bestPointSize = std::min(bestPointSizeBasedX, bestPointSizeBasedY); + bestPointSize = std::min(std::max(bestPointSize, minPointSize), maxPointSize); + bestPointSize = std::floor((bestPointSize - minPointSize) / pointInterval) * pointInterval + minPointSize; + + if(CheckForTextFit(controller, bestPointSize, layoutSize)) + { + while(bestPointSize + pointInterval <= maxPointSize && CheckForTextFit(controller, bestPointSize + pointInterval, layoutSize)) + { + bestPointSize += pointInterval; + } + } + else if(bestPointSize - pointInterval >= minPointSize) + { + do + { + bestPointSize -= pointInterval; + } while(bestPointSize - pointInterval >= minPointSize && !CheckForTextFit(controller, bestPointSize, layoutSize)); + } } model->mElideEnabled = actualellipsis; -- 2.7.4