fix incorrect calculaion of natural size in text
[platform/core/uifw/dali-toolkit.git] / dali-toolkit / internal / text / text-controller-relayouter.cpp
index 34e7053..527bb99 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2021 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2022 Samsung Electronics Co., Ltd.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -67,6 +67,8 @@ Size Controller::Relayouter::CalculateLayoutSizeOnRequiredControllerSize(Control
                                                                         SHAPE_TEXT |
                                                                         GET_GLYPH_METRICS);
 
+  const OperationsMask sizeOperations = static_cast<OperationsMask>(LAYOUT | ALIGN | REORDER);
+
   // Set the update info to relayout the whole text.
   TextUpdateInfo& textUpdateInfo = impl.mTextUpdateInfo;
   if((0 == textUpdateInfo.mNumberOfCharactersToAdd) &&
@@ -78,55 +80,71 @@ Size Controller::Relayouter::CalculateLayoutSizeOnRequiredControllerSize(Control
   textUpdateInfo.mParagraphCharacterIndex     = 0u;
   textUpdateInfo.mRequestedNumberOfCharacters = model->mLogicalModel->mText.Count();
 
-  // This is to keep Index to the first character to be updated.
-  // Then restore it after calling Clear method.
-  auto updateInfoCharIndexBackup = textUpdateInfo.mCharacterIndex;
-
   // Get a reference to the pending operations member
   OperationsMask& operationsPending = impl.mOperationsPending;
 
-  // Layout the text for the new width.
-  // Apply the pending operations, requested operations and the only once operations.
-  // Then remove onlyOnceOperations
-  operationsPending = static_cast<OperationsMask>(operationsPending | requestedOperationsMask | onlyOnceOperations);
-
-  // Make sure the model is up-to-date before layouting
-  impl.UpdateModel(static_cast<OperationsMask>(operationsPending & ~UPDATE_LAYOUT_SIZE));
-
   // Store the actual control's size to restore later.
   const Size actualControlSize = visualModel->mControlSize;
 
-  DoRelayout(impl,
-             requestedControllerSize,
-             static_cast<OperationsMask>(operationsPending & ~UPDATE_LAYOUT_SIZE),
-             calculatedLayoutSize);
+  // Whether the text control is editable
+  const bool isEditable = NULL != impl.mEventData;
 
-  // Clear the update info. This info will be set the next time the text is updated.
-  textUpdateInfo.Clear();
+  if(!isEditable)
+  {
+    impl.UpdateModel(onlyOnceOperations);
 
-  //TODO: Refactor "DoRelayout" and extract common code of size calculation without modifying attributes of mVisualModel,
-  //TODO: then calculate GlyphPositions. Lines, Size, Layout for Natural-Size
-  //TODO: and utilize the values in OperationsPending and TextUpdateInfo without changing the original one.
-  //TODO: Also it will improve performance because there is no need todo FullRelyout on the next need for layouting.
+    // Layout the text for the new width.
+    operationsPending = static_cast<OperationsMask>(operationsPending | requestedOperationsMask);
 
-  // FullRelayoutNeeded should be true because DoRelayout is MAX_FLOAT, MAX_FLOAT.
-  // By this no need to take backup and restore it.
-  textUpdateInfo.mFullRelayoutNeeded = true;
+    DoRelayout(impl,
+               requestedControllerSize,
+               static_cast<OperationsMask>(onlyOnceOperations | requestedOperationsMask),
+               calculatedLayoutSize);
 
-  // Restore mCharacterIndex. Because "Clear" set it to the maximum integer.
-  // The "CalculateTextUpdateIndices" does not work proprely because the mCharacterIndex will be greater than mPreviousNumberOfCharacters.
-  // Which apply an assumption to update only the last  paragraph. That could cause many of out of index crashes.
-  textUpdateInfo.mCharacterIndex = updateInfoCharIndexBackup;
+    textUpdateInfo.Clear();
+    textUpdateInfo.mClearAll = true;
 
-  // Do not do again the only once operations.
-  operationsPending = static_cast<OperationsMask>(operationsPending & ~onlyOnceOperations);
+    // Do not do again the only once operations.
+    operationsPending = static_cast<OperationsMask>(operationsPending & ~onlyOnceOperations);
+  }
+  else
+  {
+    // This is to keep Index to the first character to be updated.
+    // Then restore it after calling Clear method.
+    auto updateInfoCharIndexBackup = textUpdateInfo.mCharacterIndex;
 
-  // Do the size related operations again.
+    // Layout the text for the new width.
+    // Apply the pending operations, requested operations and the only once operations.
+    // Then remove onlyOnceOperations
+    operationsPending = static_cast<OperationsMask>(operationsPending | requestedOperationsMask | onlyOnceOperations);
+
+    // Make sure the model is up-to-date before layouting
+    impl.UpdateModel(static_cast<OperationsMask>(operationsPending & ~UPDATE_LAYOUT_SIZE));
+
+    DoRelayout(impl,
+               requestedControllerSize,
+               static_cast<OperationsMask>(operationsPending & ~UPDATE_LAYOUT_SIZE),
+               calculatedLayoutSize);
+
+    // Clear the update info. This info will be set the next time the text is updated.
+    textUpdateInfo.Clear();
+
+    //TODO: Refactor "DoRelayout" and extract common code of size calculation without modifying attributes of mVisualModel,
+    //TODO: then calculate GlyphPositions. Lines, Size, Layout for Natural-Size
+    //TODO: and utilize the values in OperationsPending and TextUpdateInfo without changing the original one.
+    //TODO: Also it will improve performance because there is no need todo FullRelyout on the next need for layouting.
 
-  const OperationsMask sizeOperations = static_cast<OperationsMask>(LAYOUT |
-                                                                    ALIGN |
-                                                                    REORDER);
+    // FullRelayoutNeeded should be true because DoRelayout is MAX_FLOAT, MAX_FLOAT.
+    // By this no need to take backup and restore it.
+    textUpdateInfo.mFullRelayoutNeeded = true;
+
+    // Restore mCharacterIndex. Because "Clear" set it to the maximum integer.
+    // The "CalculateTextUpdateIndices" does not work proprely because the mCharacterIndex will be greater than mPreviousNumberOfCharacters.
+    // Which apply an assumption to update only the last  paragraph. That could cause many of out of index crashes.
+    textUpdateInfo.mCharacterIndex = updateInfoCharIndexBackup;
+  }
 
+  // Do the size related operations again.
   operationsPending = static_cast<OperationsMask>(operationsPending | sizeOperations);
 
   // Restore the actual control's size.
@@ -397,8 +415,11 @@ Controller::UpdateTextType Controller::Relayouter::Relayout(Controller& controll
     textUpdateInfo.mCharacterIndex     = 0u;
   }
 
+  bool layoutDirectionChanged = false;
   if(impl.mLayoutDirection != layoutDirection)
   {
+    // Flag to indicate that the layout direction has changed.
+    layoutDirectionChanged = true;
     // Clear the update info. This info will be set the next time the text is updated.
     textUpdateInfo.mClearAll = true;
     // Apply modifications to the model
@@ -444,18 +465,42 @@ Controller::UpdateTextType Controller::Relayouter::Relayout(Controller& controll
   if(!isEditable || !controller.IsMultiLineEnabled())
   {
     // After doing the text layout, the vertical offset to place the actor in the desired position can be calculated.
-    CalculateVerticalOffset(controller, size);
+    CalculateVerticalOffset(impl, size);
+  }
+  else // TextEditor
+  {
+    // If layoutSize is bigger than size, vertical align has no meaning.
+    if(layoutSize.y < size.y)
+    {
+      CalculateVerticalOffset(impl, size);
+      if(impl.mEventData)
+      {
+        impl.mEventData->mScrollAfterDelete = false;
+      }
+    }
   }
 
   if(isEditable)
   {
-    if(newSize)
+    if(newSize || layoutDirectionChanged)
     {
-      // If there is a new size, the scroll position needs to be clamped.
+      // If there is a new size or layout direction is changed, the scroll position needs to be clamped.
       impl.ClampHorizontalScroll(layoutSize);
 
       // Update the decorator's positions is needed if there is a new size.
       impl.mEventData->mDecorator->UpdatePositions(model->mScrollPosition - offset);
+
+      // All decorator elements need to be updated.
+      if(EventData::IsEditingState(impl.mEventData->mState))
+      {
+        impl.mEventData->mScrollAfterUpdatePosition = true;
+        impl.mEventData->mUpdateCursorPosition      = true;
+        impl.mEventData->mUpdateGrabHandlePosition  = true;
+      }
+      else if(impl.mEventData->mState == EventData::SELECTING)
+      {
+        impl.mEventData->mUpdateHighlightBox = true;
+      }
     }
 
     // Move the cursor, grab handle etc.
@@ -577,12 +622,15 @@ bool Controller::Relayouter::DoRelayout(Controller::Impl& impl, const Size& size
     }
 
     // Update the visual model.
-    bool isAutoScrollEnabled = impl.mIsAutoScrollEnabled;
+    bool isAutoScrollEnabled            = impl.mIsAutoScrollEnabled;
+    bool isAutoScrollMaxTextureExceeded = impl.mIsAutoScrollMaxTextureExceeded;
+
     Size newLayoutSize;
     viewUpdated               = impl.mLayoutEngine.LayoutText(layoutParameters,
                                                 newLayoutSize,
                                                 elideTextEnabled,
                                                 isAutoScrollEnabled,
+                                                isAutoScrollMaxTextureExceeded,
                                                 ellipsisPosition);
     impl.mIsAutoScrollEnabled = isAutoScrollEnabled;
 
@@ -612,21 +660,51 @@ bool Controller::Relayouter::DoRelayout(Controller::Impl& impl, const Size& size
 
   if(NO_OPERATION != (ALIGN & operations))
   {
-    // The laid-out lines.
-    Vector<LineRun>& lines = visualModel->mLines;
+    DoRelayoutHorizontalAlignment(impl, size, startIndex, requestedNumberOfCharacters);
+    viewUpdated = true;
+  }
+#if defined(DEBUG_ENABLED)
+  std::string currentText;
+  impl.GetText(currentText);
+  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"));
+  return viewUpdated;
+}
 
-    CharacterIndex alignStartIndex                  = startIndex;
-    Length         alignRequestedNumberOfCharacters = requestedNumberOfCharacters;
+void Controller::Relayouter::DoRelayoutHorizontalAlignment(Controller::Impl&    impl,
+                                                           const Size&          size,
+                                                           const CharacterIndex startIndex,
+                                                           const Length         requestedNumberOfCharacters)
+{
+  // The visualModel
+  VisualModelPtr& visualModel = impl.mModel->mVisualModel;
 
-    // the whole text needs to be full aligned.
-    // If you do not do a full aligned, only the last line of the multiline input is aligned.
-    if(impl.mEventData && impl.mEventData->mUpdateAlignment)
-    {
-      alignStartIndex                   = 0u;
-      alignRequestedNumberOfCharacters  = impl.mModel->mLogicalModel->mText.Count();
-      impl.mEventData->mUpdateAlignment = false;
-    }
+  // The laid-out lines.
+  Vector<LineRun>& lines = visualModel->mLines;
 
+  CharacterIndex alignStartIndex                  = startIndex;
+  Length         alignRequestedNumberOfCharacters = requestedNumberOfCharacters;
+
+  // the whole text needs to be full aligned.
+  // If you do not do a full aligned, only the last line of the multiline input is aligned.
+  if(impl.mEventData && impl.mEventData->mUpdateAlignment)
+  {
+    alignStartIndex                   = 0u;
+    alignRequestedNumberOfCharacters  = impl.mModel->mLogicalModel->mText.Count();
+    impl.mEventData->mUpdateAlignment = false;
+  }
+
+  // If there is no BoundedParagraphRuns then apply the alignment of controller.
+  // Check whether the layout is single line. It's needed to apply one alignment for single-line.
+  // In single-line layout case we need to check whether to follow the alignment of controller or the first BoundedParagraph.
+  // Apply BoundedParagraph's alignment if and only if there is one BoundedParagraph contains all characters. Otherwise follow controller's alignment.
+  const bool isFollowControllerAlignment = ((impl.mModel->GetNumberOfBoundedParagraphRuns() == 0u) ||
+                                            ((Layout::Engine::SINGLE_LINE_BOX == impl.mLayoutEngine.GetLayout()) &&
+                                             (impl.mModel->GetBoundedParagraphRuns()[0].characterRun.numberOfCharacters != impl.mModel->mLogicalModel->mText.Count())));
+
+  if(isFollowControllerAlignment)
+  {
     // Need to align with the control's size as the text may contain lines
     // starting either with left to right text or right to left.
     impl.mLayoutEngine.Align(size,
@@ -637,28 +715,102 @@ bool Controller::Relayouter::DoRelayout(Controller::Impl& impl, const Size& size
                              impl.mModel->mAlignmentOffset,
                              impl.mLayoutDirection,
                              (impl.mModel->mMatchLayoutDirection != DevelText::MatchLayoutDirection::CONTENTS));
+  }
+  else
+  {
+    //Override the controller horizontal-alignment by horizontal-alignment of bounded paragraph.
+    const Length&                      numberOfBoundedParagraphRuns = impl.mModel->GetNumberOfBoundedParagraphRuns();
+    const Vector<BoundedParagraphRun>& boundedParagraphRuns         = impl.mModel->GetBoundedParagraphRuns();
+    const CharacterIndex               alignEndIndex                = alignStartIndex + alignRequestedNumberOfCharacters - 1u;
 
-    viewUpdated = true;
+    Length alignIndex               = alignStartIndex;
+    Length boundedParagraphRunIndex = 0u;
+
+    while(alignIndex <= alignEndIndex && boundedParagraphRunIndex < numberOfBoundedParagraphRuns)
+    {
+      //BP: BoundedParagraph
+      const BoundedParagraphRun& boundedParagraphRun   = boundedParagraphRuns[boundedParagraphRunIndex];
+      const CharacterIndex&      characterStartIndexBP = boundedParagraphRun.characterRun.characterIndex;
+      const Length&              numberOfCharactersBP  = boundedParagraphRun.characterRun.numberOfCharacters;
+      const CharacterIndex       characterEndIndexBP   = characterStartIndexBP + numberOfCharactersBP - 1u;
+
+      CharacterIndex                  decidedAlignStartIndex         = alignIndex;
+      Length                          decidedAlignNumberOfCharacters = alignEndIndex - alignIndex + 1u;
+      Text::HorizontalAlignment::Type decidedHorizontalAlignment     = impl.mModel->mHorizontalAlignment;
+
+      /*
+         * Shortcuts to explain indexes cases:
+         *
+         * AS: Alignment Start Index
+         * AE: Alignment End Index
+         * PS: Paragraph Start Index
+         * PE: Paragraph End Index
+         * B: BoundedParagraph Alignment
+         * M: Model Alignment
+         *
+         */
+
+      if(alignIndex < characterStartIndexBP && characterStartIndexBP <= alignEndIndex) /// AS.MMMMMM.PS--------AE
+      {
+        // Alignment from "Alignment Start Index" to index before "Paragraph Start Index" according to "Model Alignment"
+        decidedAlignStartIndex         = alignIndex;
+        decidedAlignNumberOfCharacters = characterStartIndexBP - alignIndex;
+        decidedHorizontalAlignment     = impl.mModel->mHorizontalAlignment;
+
+        // Need to re-heck the case of current bounded paragraph
+        alignIndex = characterStartIndexBP; // Shift AS to be PS
+      }
+      else if((characterStartIndexBP <= alignIndex && alignIndex <= characterEndIndexBP) ||     /// ---PS.BBBBBBB.AS.BBBBBBB.PE---
+              (characterStartIndexBP <= alignEndIndex && alignEndIndex <= characterEndIndexBP)) /// ---PS.BBBBBB.AE.BBBBBBB.PE---
+      {
+        // Alignment from "Paragraph Start Index" to "Paragraph End Index" according to "BoundedParagraph Alignment"
+        decidedAlignStartIndex         = characterStartIndexBP;
+        decidedAlignNumberOfCharacters = numberOfCharactersBP;
+        decidedHorizontalAlignment     = boundedParagraphRun.horizontalAlignmentDefined ? boundedParagraphRun.horizontalAlignment : impl.mModel->mHorizontalAlignment;
+
+        alignIndex = characterEndIndexBP + 1u; // Shift AS to be after PE direct
+        boundedParagraphRunIndex++;            // Align then check the case of next bounded paragraph
+      }
+      else
+      {
+        boundedParagraphRunIndex++; // Check the case of next bounded paragraph
+        continue;
+      }
+
+      impl.mLayoutEngine.Align(size,
+                               decidedAlignStartIndex,
+                               decidedAlignNumberOfCharacters,
+                               decidedHorizontalAlignment,
+                               lines,
+                               impl.mModel->mAlignmentOffset,
+                               impl.mLayoutDirection,
+                               (impl.mModel->mMatchLayoutDirection != DevelText::MatchLayoutDirection::CONTENTS));
+    }
+
+    //Align the remaining that is not aligned
+    if(alignIndex <= alignEndIndex)
+    {
+      impl.mLayoutEngine.Align(size,
+                               alignIndex,
+                               (alignEndIndex - alignIndex + 1u),
+                               impl.mModel->mHorizontalAlignment,
+                               lines,
+                               impl.mModel->mAlignmentOffset,
+                               impl.mLayoutDirection,
+                               (impl.mModel->mMatchLayoutDirection != DevelText::MatchLayoutDirection::CONTENTS));
+    }
   }
-#if defined(DEBUG_ENABLED)
-  std::string currentText;
-  impl.GetText(currentText);
-  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"));
-  return viewUpdated;
 }
 
-void Controller::Relayouter::CalculateVerticalOffset(Controller& controller, const Size& controlSize)
+void Controller::Relayouter::CalculateVerticalOffset(Controller::Impl& impl, const Size& controlSize)
 {
-  Controller::Impl& impl                  = *controller.mImpl;
-  ModelPtr&         model                 = impl.mModel;
-  VisualModelPtr&   visualModel           = model->mVisualModel;
-  Size              layoutSize            = model->mVisualModel->GetLayoutSize();
-  Size              oldLayoutSize         = layoutSize;
-  float             offsetY               = 0.f;
-  bool              needRecalc            = false;
-  float             defaultFontLineHeight = impl.GetDefaultFontLineHeight();
+  ModelPtr&       model                 = impl.mModel;
+  VisualModelPtr& visualModel           = model->mVisualModel;
+  Size            layoutSize            = model->mVisualModel->GetLayoutSize();
+  Size            oldLayoutSize         = layoutSize;
+  float           offsetY               = 0.f;
+  bool            needRecalc            = false;
+  float           defaultFontLineHeight = impl.GetDefaultFontLineHeight();
 
   if(fabsf(layoutSize.height) < Math::MACHINE_EPSILON_1000)
   {