Add GetCharacterIndexAtPosition API 04/284604/9
authorsarajammal <s.al-jammal@partner.samsung.com>
Mon, 21 Nov 2022 16:28:54 +0000 (19:28 +0300)
committersarajammal <s.al-jammal@partner.samsung.com>
Tue, 27 Dec 2022 09:00:06 +0000 (12:00 +0300)
Retrieve the character index based on a given visual X and Y values.

Function prototype: int GetCharacterIndexAtPosition(ModelPtr textModel, float visualX, float visualY);

Add new API to TextGeometry.

Change-Id: Idea36f92053cd475e48e15c3bbdd57c79311f4c3

13 files changed:
automated-tests/src/dali-toolkit/utc-Dali-TextGeometry.cpp
dali-toolkit/devel-api/text/text-geometry-devel.cpp
dali-toolkit/devel-api/text/text-geometry-devel.h
dali-toolkit/internal/controls/text-controls/text-editor-impl.cpp
dali-toolkit/internal/controls/text-controls/text-editor-impl.h
dali-toolkit/internal/controls/text-controls/text-field-impl.cpp
dali-toolkit/internal/controls/text-controls/text-field-impl.h
dali-toolkit/internal/controls/text-controls/text-label-impl.cpp
dali-toolkit/internal/controls/text-controls/text-label-impl.h
dali-toolkit/internal/text/controller/text-controller.cpp
dali-toolkit/internal/text/controller/text-controller.h
dali-toolkit/internal/text/text-geometry.cpp
dali-toolkit/internal/text/text-geometry.h

index 76a8b46..634d85c 100644 (file)
@@ -526,4 +526,161 @@ int UtcDaliTextGeometryWithVerticalLineAlignmentBottomGetCharacterBoundingRectan
   TestTextGeometryUtils::CheckRectGeometryResult(charGeometry, expectedCharGeometry);
 
   END_TEST;
+}
+
+int UtcDaliTextGeometryGetCharacterIndexAtPositionTextLabel(void)
+{
+  ToolkitTestApplication application;
+  tet_infoline("UtcDaliTextGeometryGetCharacterIndexAtPositionTextLabel");
+
+  TextLabel label = TextLabel::New();
+  DALI_TEST_CHECK(label);
+
+  application.GetScene().Add(label);
+
+  label.SetProperty(Actor::Property::SIZE, Vector2(450.0f, 300.f));
+  label.SetProperty(TextLabel::Property::POINT_SIZE, 10.f);
+  label.SetProperty(TextLabel::Property::TEXT, "sara سارة");
+
+  application.SendNotification();
+  application.Render();
+
+  int characterIndex = TextGeometry::GetCharacterIndexAtPosition(label, 1, 1);
+
+  int expectedCharacterIndex = 0;
+
+  DALI_TEST_EQUALS(characterIndex, expectedCharacterIndex, TEST_LOCATION);
+
+  END_TEST;
+}
+
+int UtcDaliTextGeometryGetCharacterIndexAtPositionTextEditor(void)
+{
+  ToolkitTestApplication application;
+  tet_infoline("UtcDaliTextGeometryGetCharacterIndexAtPositionTextEditor");
+
+  TextEditor editor = TextEditor::New();
+  DALI_TEST_CHECK(editor);
+
+  application.GetScene().Add(editor);
+
+  editor.SetProperty(Actor::Property::SIZE, Vector2(160.0f, 250.f));
+  editor.SetProperty(TextEditor::Property::POINT_SIZE, 10.f);
+  editor.SetProperty(TextEditor::Property::TEXT, "Hello everyone.");
+
+  application.SendNotification();
+  application.Render();
+
+  int characterIndex = TextGeometry::GetCharacterIndexAtPosition(editor, 2, 2);
+
+  int expectedCharacterIndex = 0;
+
+  DALI_TEST_EQUALS(characterIndex, expectedCharacterIndex, TEST_LOCATION);
+
+  END_TEST;
+}
+
+int UtcDaliTextGeometryGetCharacterIndexAtPositionTextField(void)
+{
+  ToolkitTestApplication application;
+  tet_infoline("UtcDaliTextGeometryGetCharacterIndexAtPositionTextField");
+
+  TextField field = TextField::New();
+  DALI_TEST_CHECK(field);
+
+  application.GetScene().Add(field);
+
+  field.SetProperty(Actor::Property::ANCHOR_POINT, AnchorPoint::CENTER);
+  field.SetProperty(Actor::Property::SIZE, Vector2(450.0f, 350.f));
+  field.SetProperty(TextField::Property::POINT_SIZE, 10.f);
+  field.SetProperty(TextField::Property::TEXT, "مرحبا بالعالم");
+
+  application.SendNotification();
+  application.Render();
+
+  int characterIndex = TextGeometry::GetCharacterIndexAtPosition(field, 18.6, 6);
+
+  int expectedCharacterIndex = 12;
+
+  DALI_TEST_EQUALS(characterIndex, expectedCharacterIndex, TEST_LOCATION);
+
+  END_TEST;
+}
+
+int UtcDaliTextGeometryGetCharacterIndexAtPositionTextLabelEmptyText(void)
+{
+  ToolkitTestApplication application;
+  tet_infoline("UtcDaliTextGeometryGetCharacterIndexAtPositionTextLabelEmptyText");
+
+  TextLabel label = TextLabel::New();
+  DALI_TEST_CHECK(label);
+
+  application.GetScene().Add(label);
+
+  label.SetProperty(Actor::Property::SIZE, Vector2(450.0f, 300.f));
+  label.SetProperty(TextLabel::Property::POINT_SIZE, 10.f);
+  label.SetProperty(TextLabel::Property::TEXT, "");
+
+  application.SendNotification();
+  application.Render();
+
+  int characterIndex = TextGeometry::GetCharacterIndexAtPosition(label, 1, 0);
+
+  int expectedCharacterIndex = -1;
+
+  DALI_TEST_EQUALS(characterIndex, expectedCharacterIndex, TEST_LOCATION);
+
+  END_TEST;
+}
+
+int UtcDaliTextGeometryGetCharacterIndexAtPositionLastCharacter(void)
+{
+  ToolkitTestApplication application;
+  tet_infoline("UtcDaliTextGeometryGetCharacterIndexAtPositionLastCharacter");
+
+  TextLabel label = TextLabel::New();
+  DALI_TEST_CHECK(label);
+
+  application.GetScene().Add(label);
+
+  label.SetProperty(Actor::Property::SIZE, Vector2(450.0f, 300.f));
+  label.SetProperty(TextLabel::Property::POINT_SIZE, 10.f);
+  label.SetProperty(TextLabel::Property::TEXT, "Hello");
+
+  application.SendNotification();
+  application.Render();
+
+  int characterIndex = TextGeometry::GetCharacterIndexAtPosition(label, 57, 2);
+
+  int expectedCharacterIndex = 4;
+
+  DALI_TEST_EQUALS(characterIndex, expectedCharacterIndex, TEST_LOCATION);
+
+  END_TEST;
+}
+
+int UtcDaliTextGeometryGetCharacterIndexAtPositionXExceedsTextWidth(void)
+{
+  ToolkitTestApplication application;
+  tet_infoline("UtcDaliTextGeometryGetCharacterIndexAtPositionXExceedsTextWidth");
+
+  TextLabel label = TextLabel::New();
+  DALI_TEST_CHECK(label);
+
+  application.GetScene().Add(label);
+
+  label.SetProperty(Actor::Property::SIZE, Vector2(450.0f, 300.f));
+  label.SetProperty(TextLabel::Property::POINT_SIZE, 10.f);
+  label.SetProperty(TextLabel::Property::TEXT, "Hello");
+
+  application.SendNotification();
+  application.Render();
+
+  int characterIndex = TextGeometry::GetCharacterIndexAtPosition(label, 150, 1);
+
+  int expectedCharacterIndex = -1;
+
+  DALI_TEST_EQUALS(characterIndex, expectedCharacterIndex, TEST_LOCATION);
+
+  END_TEST;
 }
\ No newline at end of file
index 962484e..aaecc89 100644 (file)
@@ -62,6 +62,21 @@ Rect<float> GetCharacterBoundingRectangle(TextField field, const uint32_t charIn
   return GetImpl(field).GetCharacterBoundingRectangle(charIndex);
 }
 
+int GetCharacterIndexAtPosition(TextLabel label, float visualX, float visualY)
+{
+  return GetImpl(label).GetCharacterIndexAtPosition(visualX, visualY);
+}
+
+int GetCharacterIndexAtPosition(TextField field, float visualX, float visualY)
+{
+  return GetImpl(field).GetCharacterIndexAtPosition(visualX, visualY);
+}
+
+int GetCharacterIndexAtPosition(TextEditor editor, float visualX, float visualY)
+{
+  return GetImpl(editor).GetCharacterIndexAtPosition(visualX, visualY);
+}
+
 } //namespace TextGeometry
 
 } // namespace Text
index 014a7ce..8872cfe 100644 (file)
@@ -92,6 +92,39 @@ namespace TextGeometry
    */
    DALI_TOOLKIT_API Rect<float> GetCharacterBoundingRectangle(TextEditor editor, const uint32_t charIndex);
 
+  /**
+   * @brief Get the character index.
+   * If the text is not yet rendered or the text is empty, -1 is returned.
+   *
+   * @param[in] label text model containing text info.
+   * @param[in] visualX visual x position.
+   * @param[in] visualY visual y position.
+   * @return character index.
+   */
+   DALI_TOOLKIT_API int GetCharacterIndexAtPosition(TextLabel label, float visualX, float visualY);
+
+  /**
+   * @brief Get the character index.
+   * If the text is not yet rendered or the text is empty, -1 is returned.
+   *
+   * @param[in] field text model containing text info.
+   * @param[in] visualX visual x position.
+   * @param[in] visualY visual y position.
+   * @return character index.
+   */
+  DALI_TOOLKIT_API int GetCharacterIndexAtPosition(TextField field, float visualX, float visualY);
+
+  /**
+   * @brief Get the character index.
+   * If the text is not yet rendered or the text is empty, -1 is returned.
+   *
+   * @param[in] editor text model containing text info.
+   * @param[in] visualX visual x position.
+   * @param[in] visualY visual y position.
+   * @return character index.
+   */
+  DALI_TOOLKIT_API int GetCharacterIndexAtPosition(TextEditor editor, float visualX, float visualY);
+
 
 } // namespace TextGeometry
 
index a27f45d..ea5f43c 100644 (file)
@@ -374,6 +374,11 @@ Rect<float> TextEditor::GetCharacterBoundingRectangle(const uint32_t charIndex)
   return mController->GetCharacterBoundingRectangle(charIndex);
 }
 
+int TextEditor::GetCharacterIndexAtPosition(float visualX, float visualY) const
+{
+  return mController->GetCharacterIndexAtPosition(visualX, visualY);
+}
+
 void TextEditor::SetSpannedText(const Text::Spanned& spannedText)
 {
   mController->SetSpannedText(spannedText);
index a6cd50d..5591439 100644 (file)
@@ -377,6 +377,16 @@ public:
   Rect<float> GetCharacterBoundingRectangle(const uint32_t charIndex) const;
 
   /**
+   * @brief Get the character index.
+   * If the text is not yet rendered or the text is empty, -1 is returned.
+   *
+   * @param[in] visualX visual x position.
+   * @param[in] visualY visual y position.
+   * @return character index.
+   */
+  int GetCharacterIndexAtPosition(float visualX, float visualY) const;
+
+  /**
    * @brief Set the @p spannedText into current textEditor
    * the spanned text contains content (text) and  format (spans with ranges)
    * the text is copied into text-controller and the spans are applied on ranges
index 3d53aa1..e7a8f4b 100644 (file)
@@ -1215,6 +1215,11 @@ Rect<float> TextField::GetCharacterBoundingRectangle(const uint32_t charIndex) c
   return mController->GetCharacterBoundingRectangle(charIndex);
 }
 
+int TextField::GetCharacterIndexAtPosition(float visualX, float visualY) const
+{
+  return mController->GetCharacterIndexAtPosition(visualX, visualY);
+}
+
 void TextField::SetSpannedText(const Text::Spanned& spannedText)
 {
   mController->SetSpannedText(spannedText);
index c9ef431..43bde7a 100644 (file)
@@ -386,6 +386,16 @@ public:
   Rect<float> GetCharacterBoundingRectangle(const uint32_t charIndex) const;
 
   /**
+   * @brief Get the character index.
+   * If the text is not yet rendered or the text is empty, -1 is returned.
+   *
+   * @param[in] visualX visual x position.
+   * @param[in] visualY visual y position.
+   * @return character index.
+   */
+  int GetCharacterIndexAtPosition(float visualX, float visualY) const;
+
+  /**
    * @brief Set the @p spannedText into current textField
    * the spanned text contains content (text) and  format (spans with ranges)
    * the text is copied into text-controller and the spans are applied on ranges
index 570e370..fb41c56 100644 (file)
@@ -1246,6 +1246,11 @@ Rect<float> TextLabel::GetCharacterBoundingRectangle(const uint32_t charIndex) c
   return mController->GetCharacterBoundingRectangle(charIndex);
 }
 
+int TextLabel::GetCharacterIndexAtPosition(float visualX, float visualY) const
+{
+  return mController->GetCharacterIndexAtPosition(visualX, visualY);
+}
+
 void TextLabel::SetSpannedText(const Text::Spanned& spannedText)
 {
   mController->SetSpannedText(spannedText);
index edf0a20..6e34534 100644 (file)
@@ -144,6 +144,16 @@ public:
   Rect<float> GetCharacterBoundingRectangle(const uint32_t charIndex) const;
 
   /**
+   * @brief Get the character index.
+   * If the text is not yet rendered or the textis empty, -1 is returned.
+   *
+   * @param[in] visualX visual x position.
+   * @param[in] visualY visual y position.
+   * @return character index.
+   */
+  int GetCharacterIndexAtPosition(float visualX, float visualY) const;
+
+  /**
    * @brief Set the @p spannedText into current textLabel
    * the spanned text contains content (text) and  format (spans with ranges)
    * the text is copied into text-controller and the spans are applied on ranges
index 2589266..e8d6337 100644 (file)
@@ -1431,6 +1431,11 @@ Rect<float> Controller::GetCharacterBoundingRectangle(const uint32_t charIndex)
   return GetCharacterBoundingRect(mImpl->mModel, charIndex);
 }
 
+int Controller::GetCharacterIndexAtPosition(float visualX, float visualY)
+{
+  return GetCharIndexAtPosition(mImpl->mModel, visualX, visualY);
+}
+
 Rect<> Controller::GetTextBoundingRectangle(CharacterIndex startIndex, CharacterIndex endIndex)
 {
   Vector<Vector2> sizeList;
index 85ebfaa..27b8491 100644 (file)
@@ -1761,6 +1761,16 @@ public: // Queries & retrieves.
   Rect<float> GetCharacterBoundingRectangle(const uint32_t charIndex);
 
   /**
+   * @brief Get the character index.
+   * If the text is not yet rendered or the text is empty, -1 is returned.
+   *
+   * @param[in] visualX visual x position.
+   * @param[in] visualY visual y position.
+   * @return character index.
+   */
+  int GetCharacterIndexAtPosition(float visualX, float visualY);
+
+  /**
    * @brief Gets the bounding box of a specific text range.
    *
    * @param[in] startIndex start index of the text requested to get bounding box to.
index 01ba584..8992c6d 100644 (file)
@@ -25,6 +25,7 @@
 #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;
 
@@ -400,6 +401,126 @@ Rect<> GetCharacterBoundingRect(ModelPtr textModel, const uint32_t charIndex)
   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 = -1;
+  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;
+    }
+  }
+
+  // Check if a line is not found; return -1.
+  if(lineIndex == -1)
+  {
+    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
index 6dae02a..1d410bf 100644 (file)
@@ -84,6 +84,17 @@ float GetLineTop(const Vector<LineRun>& lines, const LineRun& lineRun);
 Rect<> GetCharacterBoundingRect(ModelPtr textModel, const uint32_t charIndex);
 
 /**
+  * @brief Get the character index.
+  * If the text is not yet rendered or the text is empty, -1 is returned.
+  *
+  * @param[in] textModel text model containing text info.
+  * @param[in] visualX visual x position.
+  * @param[in] visualY visual y position.
+  * @return character index.
+  */
+int GetCharIndexAtPosition(ModelPtr textModel, float visualX, float visualY);
+
+/**
   * @brief Get the left point of the character (x).
   *
   * @param[in] glyph the requested character glyph.