X-Git-Url: http://review.tizen.org/git/?a=blobdiff_plain;f=dali-toolkit%2Finternal%2Ftext%2Fcontroller%2Ftext-controller-relayouter.cpp;h=de4c776ecb78842fe1368558c5a6cce5769e6904;hb=HEAD;hp=d323bd817653e8c7d63f8f4d3a02909d3c6da1f0;hpb=7e4bad8795710fa9b6cf1df5e8f1497b8e460d7c;p=platform%2Fcore%2Fuifw%2Fdali-toolkit.git diff --git a/dali-toolkit/internal/text/controller/text-controller-relayouter.cpp b/dali-toolkit/internal/text/controller/text-controller-relayouter.cpp index d323bd8..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 @@ -36,6 +38,7 @@ Debug::Filter* gLogFilter = Debug::Filter::New(Debug::NoLogging, true, "LOG_TEXT #endif DALI_INIT_TRACE_FILTER(gTraceFilter, DALI_TRACE_TEXT_PERFORMANCE_MARKER, false); +DALI_INIT_TRACE_FILTER(gTraceFilter2, DALI_TRACE_PERFORMANCE_MARKER, false); constexpr float MAX_FLOAT = std::numeric_limits::max(); @@ -233,22 +236,135 @@ bool Controller::Relayouter::CheckForTextFit(Controller& controller, float point // Make sure the model is up-to-date before layouting impl.UpdateModel(onlyOnceOperations); + bool layoutTooSmall = false; DoRelayout(impl, Size(layoutSize.width, MAX_FLOAT), static_cast(onlyOnceOperations | LAYOUT), - textSize); + textSize, + layoutTooSmall); // Clear the update info. This info will be set the next time the text is updated. textUpdateInfo.Clear(); textUpdateInfo.mClearAll = true; - if(textSize.width >= layoutSize.width || textSize.height >= layoutSize.height) + if(layoutTooSmall || textSize.width > layoutSize.width || textSize.height > layoutSize.height) { return false; } return true; } +void Controller::Relayouter::FitArrayPointSizeforLayout(Controller& controller, const Size& layoutSize) +{ + Controller::Impl& impl = *controller.mImpl; + + const OperationsMask operations = impl.mOperationsPending; + if(NO_OPERATION != (UPDATE_LAYOUT_SIZE & operations) || impl.mTextFitContentSize != layoutSize) + { + DALI_TRACE_SCOPE(gTraceFilter, "DALI_TEXT_FIT_ARRAY_LAYOUT"); + std::vector fitOptions = impl.mTextFitArray; + int numberOfFitOptions = static_cast(fitOptions.size()); + if(numberOfFitOptions == 0) + { + DALI_LOG_ERROR("fitOptions is empty\n"); + return; + } + + ModelPtr& model = impl.mModel; + bool actualellipsis = model->mElideEnabled; + model->mElideEnabled = false; + + // Sort in ascending order by PointSize. + std::sort(fitOptions.begin(), fitOptions.end(), compareByPointSize); + + // Decide whether to use binary search. + // If MinLineSize is not sorted in ascending order, + // binary search cannot guarantee that it will always find the best value. + bool binarySearch = true; + float prevMinLineSize = 0.0f; + for(Toolkit::DevelTextLabel::FitOption& option : fitOptions) + { + float optionMinLineSize = option.GetMinLineSize(); + if(prevMinLineSize > optionMinLineSize) + { + binarySearch = false; + break; + } + prevMinLineSize = optionMinLineSize; + } + + // Set the first FitOption(Minimum PointSize) to the best value. + // If the search does not find an optimal value, the minimum PointSize will be used to text fit. + Toolkit::DevelTextLabel::FitOption firstOption = fitOptions.front(); + bool bestSizeUpdatedLatest = false; + float bestPointSize = firstOption.GetPointSize(); + float bestMinLineSize = firstOption.GetMinLineSize(); + + if(binarySearch) + { + int left = 0u; + int right = numberOfFitOptions - 1; + + while (left <= right) + { + int mid = left + (right - left) / 2; + Toolkit::DevelTextLabel::FitOption option = fitOptions[mid]; + float testPointSize = option.GetPointSize(); + float testMinLineSize = option.GetMinLineSize(); + impl.SetDefaultLineSize(testMinLineSize); + + if(CheckForTextFit(controller, testPointSize, layoutSize)) + { + bestSizeUpdatedLatest = true; + bestPointSize = testPointSize; + bestMinLineSize = testMinLineSize; + left = mid + 1; + } + else + { + bestSizeUpdatedLatest = false; + right = mid - 1; + } + } + } + else + { + // If binary search is not possible, search sequentially starting from the largest PointSize. + for(auto it = fitOptions.rbegin(); it != fitOptions.rend(); ++it) + { + Toolkit::DevelTextLabel::FitOption option = *it; + float testPointSize = option.GetPointSize(); + float testMinLineSize = option.GetMinLineSize(); + impl.SetDefaultLineSize(testMinLineSize); + + if(CheckForTextFit(controller, testPointSize, layoutSize)) + { + bestSizeUpdatedLatest = true; + bestPointSize = testPointSize; + bestMinLineSize = testMinLineSize; + break; + } + else + { + bestSizeUpdatedLatest = false; + } + } + } + + // Best point size was not updated. re-run so the TextFit should be fitted really. + if(!bestSizeUpdatedLatest) + { + impl.SetDefaultLineSize(bestMinLineSize); + CheckForTextFit(controller, bestPointSize, layoutSize); + } + + model->mElideEnabled = actualellipsis; + impl.mFontDefaults->mFitPointSize = bestPointSize; + impl.mFontDefaults->sizeDefined = true; + impl.ClearFontData(); + } +} + void Controller::Relayouter::FitPointSizeforLayout(Controller& controller, const Size& layoutSize) { Controller::Impl& impl = *controller.mImpl; @@ -259,64 +375,148 @@ void Controller::Relayouter::FitPointSizeforLayout(Controller& controller, const DALI_TRACE_SCOPE(gTraceFilter, "DALI_TEXT_FIT_LAYOUT"); ModelPtr& model = impl.mModel; - bool actualellipsis = model->mElideEnabled; - float minPointSize = impl.mTextFitMinSize; - float maxPointSize = impl.mTextFitMaxSize; - float pointInterval = impl.mTextFitStepSize; - float currentFitPointSize = impl.mFontDefaults->mFitPointSize; + bool actualellipsis = model->mElideEnabled; + float minPointSize = impl.mTextFitMinSize; + float maxPointSize = impl.mTextFitMaxSize; + 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); - - if(CheckForTextFit(controller, testPointSize, layoutSize)) + // Ensure minPointSize + pointSizeRange * pointInverval >= maxPointSize + while(minPointSize + static_cast(pointSizeRange) * pointInterval < maxPointSize) { - bestSizeUpdatedLatest = true; + ++pointSizeRange; + } - bestSizeIndex = testIndex; - minIndex = testIndex + 1u; + 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) + { + 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; + } + 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; @@ -324,6 +524,8 @@ void Controller::Relayouter::FitPointSizeforLayout(Controller& controller, const { impl.mTextFitChanged = true; } + // Revert back to the original TextLabel LineSize. + impl.SetDefaultLineSize(currentDefaultLineSize); impl.mFontDefaults->mFitPointSize = bestPointSize; impl.mFontDefaults->sizeDefined = true; impl.ClearFontData(); @@ -343,9 +545,10 @@ float Controller::Relayouter::GetHeightForWidth(Controller& controller, float wi VisualModelPtr& visualModel = model->mVisualModel; TextUpdateInfo& textUpdateInfo = impl.mTextUpdateInfo; - Size layoutSize; + // Get cached value. + Size layoutSize = visualModel->GetHeightForWidth(); - if(fabsf(width - visualModel->mControlSize.width) > Math::MACHINE_EPSILON_1000 || + if(fabsf(width - layoutSize.width) > Math::MACHINE_EPSILON_1000 || textUpdateInfo.mFullRelayoutNeeded || textUpdateInfo.mClearAll) { @@ -355,11 +558,15 @@ float Controller::Relayouter::GetHeightForWidth(Controller& controller, float wi layoutSize = CalculateLayoutSizeOnRequiredControllerSize(controller, sizeRequestedWidthAndMaxHeight, requestedOperationsMask); + // The calculated layout width may not be the same as the requested width. + // For cache efficiency, the requested width is stored. + layoutSize.width = width; + visualModel->SetHeightForWidth(layoutSize); + DALI_LOG_INFO(gLogFilter, Debug::Verbose, "<--Controller::GetHeightForWidth calculated %f\n", layoutSize.height); } else { - layoutSize = visualModel->GetLayoutSize(); DALI_LOG_INFO(gLogFilter, Debug::Verbose, "<--Controller::GetHeightForWidth cached %f\n", layoutSize.height); } @@ -551,8 +758,14 @@ Controller::UpdateTextType Controller::Relayouter::Relayout(Controller& controll bool Controller::Relayouter::DoRelayout(Controller::Impl& impl, const Size& size, OperationsMask operationsRequired, Size& layoutSize) { + bool layoutTooSmall = false; + return DoRelayout(impl, size, operationsRequired, layoutSize, layoutTooSmall); +} + +bool Controller::Relayouter::DoRelayout(Controller::Impl& impl, const Size& size, OperationsMask operationsRequired, Size& layoutSize, bool& layoutTooSmall) +{ DALI_LOG_INFO(gLogFilter, Debug::Verbose, "-->Controller::Relayouter::DoRelayout %p size %f,%f\n", &impl, size.width, size.height); - DALI_TRACE_SCOPE(gTraceFilter, "DALI_TEXT_DORELAYOUT"); + DALI_TRACE_SCOPE(gTraceFilter2, "DALI_TEXT_DORELAYOUT"); bool viewUpdated(false); // Calculate the operations to be done. @@ -672,6 +885,7 @@ bool Controller::Relayouter::DoRelayout(Controller::Impl& impl, const Size& size isHiddenInputEnabled, ellipsisPosition); impl.mIsAutoScrollEnabled = isAutoScrollEnabled; + layoutTooSmall = !viewUpdated; viewUpdated = viewUpdated || (newLayoutSize != layoutSize); @@ -708,6 +922,7 @@ bool Controller::Relayouter::DoRelayout(Controller::Impl& impl, const Size& size DALI_LOG_INFO(gLogFilter, Debug::Concise, "Controller::Relayouter::DoRelayout [%p] mImpl->mIsTextDirectionRTL[%s] [%s]\n", &impl, (impl.mIsTextDirectionRTL) ? "true" : "false", currentText.c_str()); #endif DALI_LOG_INFO(gLogFilter, Debug::Verbose, "<--Controller::Relayouter::DoRelayout, view updated %s\n", (viewUpdated ? "true" : "false")); + DALI_LOG_INFO(gLogFilter, Debug::Verbose, "<--Controller::Relayouter::DoRelayout, layout too small %s\n", (layoutTooSmall ? "true" : "false")); return viewUpdated; }