#include <dali-toolkit/internal/text/controller/text-controller-relayouter.h>
// EXTERNAL INCLUDES
+#include <cmath>
#include <dali/integration-api/debug.h>
+#include <dali/integration-api/trace.h>
+#include <dali/public-api/common/constants.h>
+#include <dali/public-api/math/math-utils.h>
#include <limits>
// INTERNAL INCLUDES
-#include <dali-toolkit/internal/text/layouts/layout-parameters.h>
#include <dali-toolkit/internal/text/controller/text-controller-event-handler.h>
#include <dali-toolkit/internal/text/controller/text-controller-impl.h>
+#include <dali-toolkit/internal/text/layouts/layout-parameters.h>
namespace
{
Debug::Filter* gLogFilter = Debug::Filter::New(Debug::NoLogging, true, "LOG_TEXT_CONTROLS");
#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<float>::max();
float ConvertToEven(float value)
if(!isEditable)
{
+ if(NO_OPERATION != (VALIDATE_FONTS & operationsPending) &&
+ textUpdateInfo.mCharacterIndex == static_cast<CharacterIndex>(-1))
+ {
+ impl.ClearFontData();
+ updateInfoCharIndexBackup = textUpdateInfo.mCharacterIndex;
+ }
+
impl.UpdateModel(onlyOnceOperations);
// Layout the text for the new width.
Vector3 Controller::Relayouter::GetNaturalSize(Controller& controller)
{
DALI_LOG_INFO(gLogFilter, Debug::Verbose, "-->Controller::GetNaturalSize\n");
+ DALI_TRACE_SCOPE(gTraceFilter, "DALI_TEXT_GET_NATURAL_SIZE");
Vector3 naturalSizeVec3;
// Make sure the model is up-to-date before layouting
// 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<OperationsMask>(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<Toolkit::DevelTextLabel::FitOption> fitOptions = impl.mTextFitArray;
+ int numberOfFitOptions = static_cast<int>(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;
const OperationsMask operations = impl.mOperationsPending;
if(NO_OPERATION != (UPDATE_LAYOUT_SIZE & operations) || impl.mTextFitContentSize != layoutSize)
{
+ 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;
- Vector<float> pointSizeArray;
+ float bestPointSize = minPointSize;
// check zero value
if(pointInterval < 1.f)
impl.mTextFitStepSize = pointInterval = 1.0f;
}
- pointSizeArray.Reserve(static_cast<unsigned int>(ceil((maxPointSize - minPointSize) / pointInterval)));
+ uint32_t pointSizeRange = static_cast<uint32_t>(ceil((maxPointSize - minPointSize) / pointInterval));
- for(float i = minPointSize; i < maxPointSize; i += pointInterval)
+ if(isMultiLine || pointSizeRange < 3)
{
- pointSizeArray.PushBack(i);
- }
+ // Ensure minPointSize + pointSizeRange * pointInverval >= maxPointSize
+ while(minPointSize + static_cast<float>(pointSizeRange) * pointInterval < maxPointSize)
+ {
+ ++pointSizeRange;
+ }
- pointSizeArray.PushBack(maxPointSize);
+ 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<float>(testIndex) * pointInterval);
+
+ if(CheckForTextFit(controller, testPointSize, layoutSize))
+ {
+ bestSizeUpdatedLatest = true;
+
+ bestSizeIndex = testIndex;
+ minIndex = testIndex + 1u;
+ }
+ else
+ {
+ bestSizeUpdatedLatest = false;
+ maxIndex = testIndex;
+ }
+ }
+ bestPointSize = std::min(maxPointSize, minPointSize + static_cast<float>(bestSizeIndex) * pointInterval);
- int bestSizeIndex = 0;
- int min = bestSizeIndex + 1;
- int max = pointSizeArray.Size() - 1;
- while(min <= max)
+ // Best point size was not updated. re-run so the TextFit should be fitted really.
+ if(!bestSizeUpdatedLatest)
+ {
+ CheckForTextFit(controller, bestPointSize, layoutSize);
+ }
+ }
+ else
{
- int destI = (min + max) / 2;
+ // assume textSize = a * pointSize + b, finding a and b.
+ Size textSize;
+ TextUpdateInfo& textUpdateInfo = impl.mTextUpdateInfo;
+ const OperationsMask onlyOnceOperations = static_cast<OperationsMask>(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();
+
- if(CheckForTextFit(controller, pointSizeArray[destI], layoutSize))
+ // Make sure the model is up-to-date before layouting
+ impl.UpdateModel(onlyOnceOperations);
+
+ DoRelayout(impl,
+ Size(layoutSize.width, MAX_FLOAT),
+ static_cast<OperationsMask>(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))
{
- bestSizeIndex = min;
- min = destI + 1;
+ while(bestPointSize + pointInterval <= maxPointSize && CheckForTextFit(controller, bestPointSize + pointInterval, layoutSize))
+ {
+ bestPointSize += pointInterval;
+ }
}
- else
+ else if(bestPointSize - pointInterval >= minPointSize)
{
- max = destI - 1;
- bestSizeIndex = max;
+ do
+ {
+ bestPointSize -= pointInterval;
+ } while(bestPointSize - pointInterval >= minPointSize && !CheckForTextFit(controller, bestPointSize, layoutSize));
}
}
model->mElideEnabled = actualellipsis;
- if(currentFitPointSize != pointSizeArray[bestSizeIndex])
+ if(!Dali::Equals(currentFitPointSize, bestPointSize))
{
impl.mTextFitChanged = true;
}
- impl.mFontDefaults->mFitPointSize = pointSizeArray[bestSizeIndex];
+ // Revert back to the original TextLabel LineSize.
+ impl.SetDefaultLineSize(currentDefaultLineSize);
+ impl.mFontDefaults->mFitPointSize = bestPointSize;
impl.mFontDefaults->sizeDefined = true;
impl.ClearFontData();
}
float Controller::Relayouter::GetHeightForWidth(Controller& controller, float width)
{
DALI_LOG_INFO(gLogFilter, Debug::Verbose, "-->Controller::GetHeightForWidth %p width %f\n", &controller, width);
+ DALI_TRACE_SCOPE(gTraceFilter, "DALI_TEXT_GET_HEIGHT_FOR_WIDTH");
// Make sure the model is up-to-date before layouting
EventHandler::ProcessModifyEvents(controller);
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)
{
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);
}
TextUpdateInfo& textUpdateInfo = impl.mTextUpdateInfo;
DALI_LOG_INFO(gLogFilter, Debug::Verbose, "-->Controller::Relayout %p size %f,%f, autoScroll[%s]\n", &controller, size.width, size.height, impl.mIsAutoScrollEnabled ? "true" : "false");
+ DALI_TRACE_SCOPE(gTraceFilter, "DALI_TEXT_RELAYOUT");
UpdateTextType updateTextType = NONE_UPDATED;
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(gTraceFilter2, "DALI_TEXT_DORELAYOUT");
bool viewUpdated(false);
// Calculate the operations to be done.
// Update the visual model.
bool isAutoScrollEnabled = impl.mIsAutoScrollEnabled;
bool isAutoScrollMaxTextureExceeded = impl.mIsAutoScrollMaxTextureExceeded;
+ bool isHiddenInputEnabled = false;
+ if(impl.mHiddenInput && impl.mEventData != nullptr && impl.mHiddenInput->GetHideMode() != Toolkit::HiddenInput::Mode::HIDE_NONE)
+ {
+ isHiddenInputEnabled = true;
+ }
Size newLayoutSize;
viewUpdated = impl.mLayoutEngine.LayoutText(layoutParameters,
elideTextEnabled,
isAutoScrollEnabled,
isAutoScrollMaxTextureExceeded,
+ isHiddenInputEnabled,
ellipsisPosition);
impl.mIsAutoScrollEnabled = isAutoScrollEnabled;
+ layoutTooSmall = !viewUpdated;
viewUpdated = viewUpdated || (newLayoutSize != layoutSize);
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;
}
// Whether the text control is editable
const bool isEditable = NULL != impl.mEventData;
- if(isEditable && layoutSize.height != defaultFontLineHeight && impl.IsShowingPlaceholderText())
+ if(isEditable && !Dali::Equals(layoutSize.height, defaultFontLineHeight) && impl.IsShowingPlaceholderText())
{
// This code prevents the wrong positioning of cursor when the layout size is bigger/smaller than defaultFontLineHeight.
// This situation occurs when the size of placeholder text is different from the default text.