[dali_2.3.22] Merge branch 'devel/master'
[platform/core/uifw/dali-toolkit.git] / dali-toolkit / internal / text / text-geometry.cpp
index 9fa5ad1..3635d0c 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.
 
 // EXTERNAL INCLUDES
 #include <dali/integration-api/debug.h>
+#include <dali/public-api/math/math-utils.h>
 
 // INTERNAL INCLUDES
 #include <dali-toolkit/internal/text/cursor-helper-functions.h>
+#include <dali-toolkit/internal/text/line-run.h>
+#include <dali-toolkit/internal/text/visual-model-impl.h>
+#include <algorithm>
 
 using namespace Dali;
 
@@ -32,13 +36,14 @@ namespace Toolkit
 {
 namespace Text
 {
-bool GetNextLine(GlyphIndex index, LineIndex& lineIndex, LineRun*& lineRun, GlyphIndex& lastGlyphOfLine, Length numberOfLines)
+bool GetNextLine(GlyphIndex index, LineIndex& lineIndex, LineRun*& lineRun, GlyphIndex& lastGlyphOfLine, Length numberOfLines, bool& isLastLine)
 {
   if(index == lastGlyphOfLine)
   {
     ++lineIndex;
     if(lineIndex < numberOfLines)
     {
+      isLastLine = (lineIndex + 1 == numberOfLines);
       ++lineRun;
       return true;
     }
@@ -47,11 +52,11 @@ bool GetNextLine(GlyphIndex index, LineIndex& lineIndex, LineRun*& lineRun, Glyp
   return false;
 }
 
-void UpdateLineInfo(const LineRun* lineRun, float& currentLineOffset, float& currentLineHeight, GlyphIndex& lastGlyphOfLine)
+void UpdateLineInfo(const LineRun* lineRun, float& currentLineOffset, float& currentLineHeight, GlyphIndex& lastGlyphOfLine, bool isLastLine)
 {
   lastGlyphOfLine   = lineRun->glyphRun.glyphIndex + lineRun->glyphRun.numberOfGlyphs - 1u;
   currentLineOffset = currentLineOffset + currentLineHeight;
-  currentLineHeight = GetLineHeight(*lineRun);
+  currentLineHeight = GetLineHeight(*lineRun, isLastLine);
 }
 
 void GetTextGeometry(ModelPtr textModel, CharacterIndex startIndex, CharacterIndex endIndex, Vector<Vector2>& sizesList, Vector<Vector2>& positionsList)
@@ -67,14 +72,29 @@ void GetTextGeometry(ModelPtr textModel, CharacterIndex startIndex, CharacterInd
   const CharacterIndex* const     glyphToCharacterBuffer         = visualModel->mGlyphsToCharacters.Begin();
   const CharacterDirection* const modelCharacterDirectionsBuffer = (0u != logicalModel->mCharacterDirections.Count()) ? logicalModel->mCharacterDirections.Begin() : NULL;
 
+  //Clear the lists
+  sizesList.Clear();
+  positionsList.Clear();
+
+  if(charactersToGlyphBuffer == nullptr || glyphsPerCharacterBuffer == nullptr || charactersPerGlyphBuffer == nullptr || glyphToCharacterBuffer == nullptr)
+  {
+    return;
+  }
+
   if(startIndex >= logicalModel->mText.Count() && endIndex >= logicalModel->mText.Count())
+  {
     return;
+  }
 
   if(startIndex >= logicalModel->mText.Count())
-    startIndex = logicalModel->mText.Count() - 1;
+  {
+    startIndex = static_cast<CharacterIndex>(logicalModel->mText.Count() - 1u);
+  }
 
   if(endIndex >= logicalModel->mText.Count())
-    endIndex = logicalModel->mText.Count() - 1;
+  {
+    endIndex = static_cast<CharacterIndex>(logicalModel->mText.Count() - 1u);
+  }
 
   if(startIndex > endIndex)
   {
@@ -100,6 +120,7 @@ void GetTextGeometry(ModelPtr textModel, CharacterIndex startIndex, CharacterInd
   GlyphIndex   glyphEnd       = *(charactersToGlyphBuffer + endIndex) + ((numberOfGlyphs > 0) ? numberOfGlyphs - 1u : 0u);
   LineIndex    lineIndex      = visualModel->GetLineOfCharacter(startIndex);
   Length       numberOfLines  = visualModel->GetTotalNumberOfLines();
+  bool         isLastLine     = lineIndex + 1 == numberOfLines;
 
   LineIndex firstLineIndex = lineIndex;
   Size      textInLineSize;
@@ -109,7 +130,7 @@ void GetTextGeometry(ModelPtr textModel, CharacterIndex startIndex, CharacterInd
 
   //get the first line and its vertical offset
   float      currentLineOffset = CalculateLineOffset(visualModel->mLines, firstLineIndex);
-  float      currentLineHeight = GetLineHeight(*lineRun);
+  float      currentLineHeight = GetLineHeight(*lineRun, isLastLine);
   GlyphIndex lastGlyphOfLine   = lineRun->glyphRun.glyphIndex + lineRun->glyphRun.numberOfGlyphs - 1;
 
   // Check if the first/last glyph is a ligature that needs be splitted like English fi or Arabic ﻻ.
@@ -148,9 +169,9 @@ void GetTextGeometry(ModelPtr textModel, CharacterIndex startIndex, CharacterInd
             positionsList.PushBack(blockPos);
           }
 
-          if(GetNextLine(index, lineIndex, lineRun, lastGlyphOfLine, numberOfLines))
+          if(GetNextLine(index, lineIndex, lineRun, lastGlyphOfLine, numberOfLines, isLastLine))
           {
-            UpdateLineInfo(lineRun, currentLineOffset, currentLineHeight, lastGlyphOfLine);
+            UpdateLineInfo(lineRun, currentLineOffset, currentLineHeight, lastGlyphOfLine, isLastLine);
           }
           // Ignore any glyph that was removed
           continue;
@@ -158,16 +179,16 @@ void GetTextGeometry(ModelPtr textModel, CharacterIndex startIndex, CharacterInd
       }
       else
       {
-        if((ellipsisPosition == DevelText::EllipsisPosition::END) && (index >= endIndexOfGlyphs))
+        if((ellipsisPosition == DevelText::EllipsisPosition::END) && (index > endIndexOfGlyphs))
         {
           //skip remaining elided glyphs
           break;
         }
         else if((ellipsisPosition == DevelText::EllipsisPosition::START) && (index <= startIndexOfGlyphs))
         {
-          if(GetNextLine(index, lineIndex, lineRun, lastGlyphOfLine, numberOfLines))
+          if(GetNextLine(index, lineIndex, lineRun, lastGlyphOfLine, numberOfLines, isLastLine))
           {
-            UpdateLineInfo(lineRun, currentLineOffset, currentLineHeight, lastGlyphOfLine);
+            UpdateLineInfo(lineRun, currentLineOffset, currentLineHeight, lastGlyphOfLine, isLastLine);
           }
 
           continue;
@@ -214,9 +235,9 @@ void GetTextGeometry(ModelPtr textModel, CharacterIndex startIndex, CharacterInd
       currentSize.y     = currentLineHeight;
 
       // if there is next line to retrieve.
-      if(GetNextLine(index, lineIndex, lineRun, lastGlyphOfLine, numberOfLines))
+      if(GetNextLine(index, lineIndex, lineRun, lastGlyphOfLine, numberOfLines, isLastLine))
       {
-        UpdateLineInfo(lineRun, currentLineOffset, currentLineHeight, lastGlyphOfLine);
+        UpdateLineInfo(lineRun, currentLineOffset, currentLineHeight, lastGlyphOfLine, isLastLine);
       }
     }
 
@@ -225,7 +246,7 @@ void GetTextGeometry(ModelPtr textModel, CharacterIndex startIndex, CharacterInd
       blockPos  = currentPosition;
       blockSize = currentSize;
     }
-    else if((isPrevoiusRightToLeft != isCurrentRightToLeft) || (blockPos.y != currentPosition.y)) //new direction or new line
+    else if((isPrevoiusRightToLeft != isCurrentRightToLeft) || (!Dali::Equals(blockPos.y, currentPosition.y))) //new direction or new line
     {
       sizesList.PushBack(blockSize);
       positionsList.PushBack(blockPos);
@@ -251,6 +272,249 @@ void GetTextGeometry(ModelPtr textModel, CharacterIndex startIndex, CharacterInd
   positionsList.PushBack(blockPos);
 }
 
+float GetLineLeft(const LineRun& lineRun)
+{
+  return lineRun.alignmentOffset;
+}
+
+float GetLineTop(const Vector<LineRun>& lines, const LineRun& lineRun)
+{
+  float     lineTop       = 0;
+  const int numberOfLines = (int)lines.Count();
+
+  int                            currentLineIndex = 0;
+  Vector<LineRun>::ConstIterator endIt            = (&lineRun);
+  for(Vector<LineRun>::Iterator it = lines.Begin();
+      it != endIt;
+      ++it, ++currentLineIndex)
+  {
+    LineRun& line       = *it;
+    bool     isLastLine = (currentLineIndex + 1) == numberOfLines;
+    lineTop += GetLineHeight(line, isLastLine);
+  }
+
+  return lineTop;
+}
+
+float GetLineWidth(const LineRun& lineRun)
+{
+  return lineRun.width;
+}
+
+Rect<float> GetLineBoundingRect(ModelPtr textModel, const uint32_t lineIndex)
+{
+  if(textModel->mVisualModel == nullptr)
+  {
+    return {0, 0, 0, 0};
+  }
+
+  Length numberOfLines = textModel->mVisualModel->GetTotalNumberOfLines();
+
+  if(lineIndex >= numberOfLines)
+  {
+    return {0, 0, 0, 0};
+  }
+
+  const Vector<LineRun>& lines       = textModel->mVisualModel->mLines;
+  const LineRun&         lineRun     = lines[lineIndex];
+  bool                   isFirstLine = lineIndex == 0;
+  bool                   isLastLine  = (lineIndex + 1) == numberOfLines;
+
+  // Calculate the Left(lineX) = X position.
+  float lineX = GetLineLeft(lineRun) + textModel->mScrollPosition.x;
+
+  // Calculate the Top(lineY) = PreviousHeights.
+  // If the line is the first line of the text; its top = 0.
+  float lineY = (isFirstLine ? 0 : GetLineTop(lines, lineRun)) + textModel->mScrollPosition.y;
+
+  // The rectangle contains the width and height:
+  float lineWidth  = GetLineWidth(lineRun);
+  float lineHeight = GetLineHeight(lineRun, isLastLine);
+
+  return {lineX, lineY, lineWidth, lineHeight};
+}
+
+float GetCharacterLeft(const GlyphInfo& glyph, const Vector2& characterPosition)
+{
+  return characterPosition.x - glyph.xBearing;
+}
+
+float GetCharacterTop(const float& yPosition)
+{
+  return (-1 * yPosition);
+}
+
+float GetCharacterHeight(const GlyphInfo& glyph)
+{
+  return glyph.height;
+}
+
+float GetCharacterWidth(const GlyphInfo& glyph)
+{
+  return glyph.advance;
+}
+
+Rect<> GetCharacterBoundingRect(ModelPtr textModel, const uint32_t charIndex)
+{
+  if(textModel->mVisualModel == nullptr)
+  {
+    return {0, 0, 0, 0};
+  }
+
+  VisualModelPtr&  visualModel  = textModel->mVisualModel;
+  LogicalModelPtr& logicalModel = textModel->mLogicalModel;
+
+  if(charIndex >= logicalModel->mText.Count() || visualModel->mLines.Empty())
+  {
+    return {0, 0, 0, 0};
+  }
+
+  const Vector<Vector2>&   glyphPositions = visualModel->mGlyphPositions;
+  const Vector<GlyphInfo>& glyphs         = visualModel->mGlyphs;
+  const Vector<LineRun>&   lines          = visualModel->mLines;
+
+  //For each character, the index of the first glyph.
+  const GlyphIndex glyphIndex = visualModel->mCharactersToGlyph[charIndex]; //took its glyphs
+
+  const Vector2&   characterPosition = glyphPositions[glyphIndex];
+  const GlyphInfo& glyphInfo         = glyphs[glyphIndex];
+
+  // GetLineOfCharacter function returns 0 if the lines are empty
+  const int      lineIndex = visualModel->GetLineOfCharacter(charIndex);
+  const LineRun& lineRun   = lines[lineIndex];
+
+  //Calculate the left: x position of the glyph + alignmentOffset of the line +  mScrollPosition.x.
+  float characterX = lineRun.alignmentOffset + GetCharacterLeft(glyphInfo, characterPosition) + textModel->mScrollPosition.x;
+
+  //Calculate the Top(characterY): position.Y + previouse lines height + mScrollPosition.y.
+  bool isFirstLine = lineIndex == 0;
+
+  //If the line is the first line of the text; its top = 0.
+  float lineY = (isFirstLine ? 0 : GetLineTop(lines, lineRun));
+
+  float characterY = lineY + GetCharacterTop(characterPosition.y) + textModel->mScrollPosition.y;
+
+  //The rectangle contains the width and height:
+  float characterWidth  = GetCharacterWidth(glyphInfo);
+  float characterHeight = GetCharacterHeight(glyphInfo);
+
+  return {characterX, characterY, characterWidth, characterHeight};
+}
+
+int GetCharIndexAtPosition(ModelPtr textModel, float visualX, float visualY)
+{
+  if(textModel == nullptr)
+  {
+    return -1;
+  }
+
+  VisualModelPtr& visualModel = textModel->mVisualModel;
+
+  const int totalNumberOfGlyphs = visualModel->mGlyphs.Count();
+  const int totalNumberOfLines  = visualModel->mLines.Count();
+
+  if((0 == totalNumberOfGlyphs) ||
+     (0 == totalNumberOfLines))
+  {
+    return -1;
+  }
+
+  //The top point of the view = 0.
+  if(visualY < 0)
+  {
+    return -1;
+  }
+
+  const Vector<LineRun>& lines = visualModel->mLines;
+
+  float lineTop   = 0.f;
+  int   lineIndex = 0;
+  int   high      = totalNumberOfLines;
+  int   low       = -1;
+  int   guess;
+
+  // Searching for the correct line.
+  while(high - low > 1)
+  {
+    guess = (high + low) / 2;
+    Vector<LineRun>::ConstIterator it = lines.Begin() + guess;
+
+    lineTop = GetLineTop(lines, *it);
+
+    if(lineTop > visualY)
+    {
+      high = guess;
+    }
+    else
+    {
+      low = guess;
+    }
+  }
+
+  if(low < 0)
+  {
+    lineIndex = 0;
+  }
+  else
+  {
+    lineIndex = low;
+  }
+
+  bool isLastLine = lineIndex + 1 == totalNumberOfLines;
+
+  if(isLastLine)
+  {
+    const LineRun& line = *(visualModel->mLines.Begin() + lineIndex);
+    float lineHeight = GetLineHeight(line, isLastLine);
+
+    // If the visualY is placed after the last line.
+    if(visualY > lineTop + lineHeight)
+    {
+      return -1;
+    }
+  }
+
+ // Start searching for the visualX
+  const LineRun& line = *(visualModel->mLines.Begin() + lineIndex);
+
+  visualX -= line.alignmentOffset;
+
+  // Positions of the glyphs
+  const Vector2* const positionsBuffer = visualModel->mGlyphPositions.Begin();
+
+  const CharacterIndex startCharacter = line.characterRun.characterIndex;
+  const CharacterIndex endCharacter   = line.characterRun.characterIndex + line.characterRun.numberOfCharacters - 1;
+
+  CharacterIndex characterIndexAtPosition = -1;
+  CharacterIndex characterIndex           = startCharacter;
+  float characterPosition;
+  float rightMostCharacterPosition;
+
+  for(; characterIndex != endCharacter; characterIndex++)
+  {
+    characterPosition = positionsBuffer[characterIndex].x;
+    rightMostCharacterPosition = positionsBuffer[characterIndex+1].x;
+
+    if(visualX < rightMostCharacterPosition && visualX >= characterPosition)
+    {
+      characterIndexAtPosition = characterIndex;
+      break;
+    }
+  }
+
+  if(characterIndex == endCharacter)
+  {
+    // If the visualX is within the last character position or it comes after the last character; we return the last character.
+    rightMostCharacterPosition = positionsBuffer[endCharacter].x + GetCharacterWidth(visualModel->mGlyphs[endCharacter]);
+
+    if(visualX >= positionsBuffer[endCharacter].x && visualX < rightMostCharacterPosition)
+    {
+      characterIndexAtPosition = endCharacter;
+    }
+  }
+
+  return characterIndexAtPosition;
+}
 } // namespace Text
 
 } // namespace Toolkit