[dali_2.3.24] Merge branch 'devel/master'
[platform/core/uifw/dali-toolkit.git] / dali-toolkit / internal / text / controller / text-controller-relayouter.cpp
index 572d830..de4c776 100644 (file)
 #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>
 
@@ -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<float>::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<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;
@@ -265,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<uint32_t>(ceil((maxPointSize - minPointSize) / pointInterval));
 
-    // Ensure minPointSize + pointSizeRange * pointInverval >= maxPointSize
-    while(minPointSize + static_cast<float>(pointSizeRange) * pointInterval < maxPointSize)
-    {
-      ++pointSizeRange;
-    }
+    uint32_t pointSizeRange = static_cast<uint32_t>(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<float>(testIndex) * pointInterval);
-
-      if(CheckForTextFit(controller, testPointSize, layoutSize))
+      // Ensure minPointSize + pointSizeRange * pointInverval >= maxPointSize
+      while(minPointSize + static_cast<float>(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<float>(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<float>(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<float>(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<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();
+
+
+        // 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))
+      {
+        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;
@@ -348,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)
   {
@@ -360,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);
   }
 
@@ -556,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.
@@ -677,6 +885,7 @@ bool Controller::Relayouter::DoRelayout(Controller::Impl& impl, const Size& size
                                                 isHiddenInputEnabled,
                                                 ellipsisPosition);
     impl.mIsAutoScrollEnabled = isAutoScrollEnabled;
+    layoutTooSmall = !viewUpdated;
 
     viewUpdated = viewUpdated || (newLayoutSize != layoutSize);
 
@@ -713,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;
 }