Dali-Text: Keyboard Shortcuts
[platform/core/uifw/dali-toolkit.git] / dali-toolkit / internal / text / cursor-helper-functions.cpp
index 10c1e22..19db7ae 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2016 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2017 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.
@@ -33,6 +33,97 @@ namespace
 
 const Dali::Toolkit::Text::CharacterDirection LTR = false; ///< Left To Right direction.
 
+struct FindWordData
+{
+  FindWordData( const Dali::Toolkit::Text::Character* const textBuffer,
+                Dali::Toolkit::Text::Length totalNumberOfCharacters,
+                Dali::Toolkit::Text::CharacterIndex hitCharacter,
+                bool isWhiteSpace,
+                bool isNewParagraph )
+  : textBuffer( textBuffer ),
+    totalNumberOfCharacters( totalNumberOfCharacters ),
+    hitCharacter( hitCharacter ),
+    foundIndex( 0 ),
+    isWhiteSpace( isWhiteSpace ),
+    isNewParagraph( isNewParagraph )
+  {}
+
+  ~FindWordData()
+  {}
+
+  const Dali::Toolkit::Text::Character* const textBuffer;
+  Dali::Toolkit::Text::Length                 totalNumberOfCharacters;
+  Dali::Toolkit::Text::CharacterIndex         hitCharacter;
+  Dali::Toolkit::Text::CharacterIndex         foundIndex;
+  bool                                        isWhiteSpace   : 1u;
+  bool                                        isNewParagraph : 1u;
+};
+
+bool IsWhiteSpaceOrNewParagraph( Dali::Toolkit::Text::Character character,
+                                 bool isHitWhiteSpace,
+                                 bool isHitWhiteSpaceOrNewParagraph )
+{
+  bool isWhiteSpaceOrNewParagraph = false;
+  if( isHitWhiteSpaceOrNewParagraph )
+  {
+    if( isHitWhiteSpace )
+    {
+      // Whether the current character is a white space. Note a new paragraph character is a white space as well but here is not wanted.
+      isWhiteSpaceOrNewParagraph = Dali::TextAbstraction::IsWhiteSpace( character ) && !Dali::TextAbstraction::IsNewParagraph( character );
+    }
+    else
+    {
+      // Whether the current character is a new paragraph character.
+      isWhiteSpaceOrNewParagraph = Dali::TextAbstraction::IsNewParagraph( character );
+    }
+  }
+  else
+  {
+    // Whether the current character is a white space or a new paragraph character (note the new paragraph character is a white space as well).
+    isWhiteSpaceOrNewParagraph = Dali::TextAbstraction::IsWhiteSpace( character );
+  }
+
+  return isWhiteSpaceOrNewParagraph;
+}
+
+void FindStartOfWord( FindWordData& data )
+{
+  const bool isHitWhiteSpaceOrNewParagraph = data.isWhiteSpace || data.isNewParagraph;
+
+  for( data.foundIndex = data.hitCharacter; data.foundIndex > 0; --data.foundIndex )
+  {
+    const Dali::Toolkit::Text::Character character = *( data.textBuffer + data.foundIndex - 1u );
+
+    const bool isWhiteSpaceOrNewParagraph = IsWhiteSpaceOrNewParagraph( character,
+                                                                        data.isWhiteSpace,
+                                                                        isHitWhiteSpaceOrNewParagraph );
+
+    if( isHitWhiteSpaceOrNewParagraph != isWhiteSpaceOrNewParagraph )
+    {
+      break;
+    }
+  }
+}
+
+void FindEndOfWord( FindWordData& data )
+{
+  const bool isHitWhiteSpaceOrNewParagraph = data.isWhiteSpace || data.isNewParagraph;
+
+  for( data.foundIndex = data.hitCharacter + 1u; data.foundIndex < data.totalNumberOfCharacters; ++data.foundIndex )
+  {
+    const Dali::Toolkit::Text::Character character = *( data.textBuffer + data.foundIndex );
+
+    const bool isWhiteSpaceOrNewParagraph = IsWhiteSpaceOrNewParagraph( character,
+                                                                        data.isWhiteSpace,
+                                                                        isHitWhiteSpaceOrNewParagraph );
+
+    if( isHitWhiteSpaceOrNewParagraph != isWhiteSpaceOrNewParagraph )
+    {
+      break;
+    }
+  }
+}
+
 } //namespace
 
 namespace Dali
@@ -45,10 +136,17 @@ namespace Text
 {
 
 LineIndex GetClosestLine( VisualModelPtr visualModel,
-                          float visualY )
+                          float visualY,
+                          bool& matchedLine )
 {
   float totalHeight = 0.f;
-  LineIndex lineIndex = 0u;
+  LineIndex lineIndex = 0;
+  matchedLine = false;
+
+  if( visualY < 0.f )
+  {
+    return 0;
+  }
 
   const Vector<LineRun>& lines = visualModel->mLines;
 
@@ -65,16 +163,17 @@ LineIndex GetClosestLine( VisualModelPtr visualModel,
 
     if( visualY < totalHeight )
     {
+      matchedLine = true;
       return lineIndex;
     }
   }
 
-  if( lineIndex == 0u )
+  if( lineIndex == 0 )
   {
     return 0;
   }
 
-  return lineIndex-1;
+  return lineIndex - 1u;
 }
 
 float CalculateLineOffset( const Vector<LineRun>& lines,
@@ -101,23 +200,38 @@ CharacterIndex GetClosestCursorIndex( VisualModelPtr visualModel,
                                       LogicalModelPtr logicalModel,
                                       MetricsPtr metrics,
                                       float visualX,
-                                      float visualY )
+                                      float visualY,
+                                      CharacterHitTest::Mode mode,
+                                      bool& matchedCharacter )
 {
   DALI_LOG_INFO( gLogFilter, Debug::Verbose, "GetClosestCursorIndex, closest visualX %f visualY %f\n", visualX, visualY );
 
-  CharacterIndex logicalIndex = 0u;
+  // Whether there is a hit on a glyph.
+  matchedCharacter = false;
+
+  CharacterIndex logicalIndex = 0;
 
-  const Length numberOfGlyphs = visualModel->mGlyphs.Count();
-  const Length numberOfLines  = visualModel->mLines.Count();
-  if( ( 0 == numberOfGlyphs ) ||
-      ( 0 == numberOfLines ) )
+  const Length totalNumberOfGlyphs = visualModel->mGlyphs.Count();
+  const Length totalNumberOfLines  = visualModel->mLines.Count();
+  if( ( 0 == totalNumberOfGlyphs ) ||
+      ( 0 == totalNumberOfLines ) )
   {
     return logicalIndex;
   }
 
+  // Whether there is a hit on a line.
+  bool matchedLine = false;
+
   // Find which line is closest.
   const LineIndex lineIndex = Text::GetClosestLine( visualModel,
-                                                    visualY );
+                                                    visualY,
+                                                    matchedLine );
+
+  if( !matchedLine && ( CharacterHitTest::TAP == mode ) )
+  {
+    // Return the first or the last character if the touch point doesn't hit a line.
+    return ( visualY < 0.f ) ? 0 : logicalModel->mText.Count();
+  }
 
   // Convert from text's coords to line's coords.
   const LineRun& line = *( visualModel->mLines.Begin() + lineIndex );
@@ -150,12 +264,12 @@ CharacterIndex GetClosestCursorIndex( VisualModelPtr visualModel,
   // The character's direction buffer.
   const CharacterDirection* const directionsBuffer = bidiLineFetched ? logicalModel->mCharacterDirections.Begin() : NULL;
 
-  // Whether there is a hit on a glyph.
-  bool matched = false;
+  // Whether the touch point if before the first glyph.
+  bool isBeforeFirstGlyph = false;
 
   // Traverses glyphs in visual order. To do that use the visual to logical conversion table.
   CharacterIndex visualIndex = startCharacter;
-  Length numberOfVisualCharacters = 0u;
+  Length numberOfVisualCharacters = 0;
   for( ; visualIndex < endCharacter; ++visualIndex )
   {
     // The character in logical order.
@@ -166,7 +280,7 @@ CharacterIndex GetClosestCursorIndex( VisualModelPtr visualModel,
     const Length numberOfGlyphs = *( glyphsPerCharacterBuffer + characterLogicalOrderIndex );
     ++numberOfVisualCharacters;
 
-    if( 0u != numberOfGlyphs )
+    if( 0 != numberOfGlyphs )
     {
       // Get the first character/glyph of the group of glyphs.
       const CharacterIndex firstVisualCharacterIndex = 1u + visualIndex - numberOfVisualCharacters;
@@ -184,14 +298,54 @@ CharacterIndex GetClosestCursorIndex( VisualModelPtr visualModel,
       // Get the position of the first glyph.
       const Vector2& position = *( positionsBuffer + firstLogicalGlyphIndex );
 
-      // Whether the glyph can be split, like Latin ligatures fi, ff or Arabic ﻻ.
-      const Length numberOfCharacters = *( charactersPerGlyphBuffer + firstLogicalGlyphIndex );
+      if( startCharacter == visualIndex )
+      {
+        const float glyphPosition = -glyphMetrics.xBearing + position.x;
+
+        if( visualX < glyphPosition )
+        {
+          isBeforeFirstGlyph = true;
+          break;
+        }
+      }
+
+      // Whether the glyph can be split, like Latin ligatures fi, ff or Arabic (ل + ا).
+      Length numberOfCharacters = *( charactersPerGlyphBuffer + firstLogicalGlyphIndex );
       if( direction != LTR )
       {
         // As characters are being traversed in visual order,
         // for right to left ligatures, the character which contains the
         // number of glyphs in the table is found first.
         // Jump the number of characters to the next glyph is needed.
+
+        if( 0 == numberOfCharacters )
+        {
+          // TODO: This is a workaround to fix an issue with complex characters in the arabic
+          // script like i.e. رّ or الأَبْجَدِيَّة العَرَبِيَّة
+          // There are characters that are not shaped in one glyph but in combination with
+          // the next one generates two of them.
+          // The visual to logical conversion table have characters in different order than
+          // expected even if all of them are arabic.
+
+          // The workaround doesn't fix the issue completely but it prevents the application
+          // to hang in an infinite loop.
+
+          // Find the number of characters.
+          for( GlyphIndex index = firstLogicalGlyphIndex + 1u;
+               ( 0 == numberOfCharacters ) && ( index < totalNumberOfGlyphs );
+               ++index )
+          {
+            numberOfCharacters = *( charactersPerGlyphBuffer + index );
+          }
+
+          if( 2u > numberOfCharacters )
+          {
+            continue;
+          }
+
+          --numberOfCharacters;
+        }
+
         visualIndex += numberOfCharacters - 1u;
       }
 
@@ -202,7 +356,7 @@ CharacterIndex GetClosestCursorIndex( VisualModelPtr visualModel,
       const Length numberOfBlocks = isInterglyphIndex ? numberOfCharacters : 1u;
       const float glyphAdvance = glyphMetrics.advance / static_cast<float>( numberOfBlocks );
 
-      CharacterIndex index = 0u;
+      CharacterIndex index = 0;
       for( ; index < numberOfBlocks; ++index )
       {
         // Find the mid-point of the area containing the glyph
@@ -210,31 +364,39 @@ CharacterIndex GetClosestCursorIndex( VisualModelPtr visualModel,
 
         if( visualX < glyphCenter )
         {
-          matched = true;
+          matchedCharacter = true;
           break;
         }
       }
 
-      if( matched )
+      if( matchedCharacter )
       {
         // If the glyph is shaped from more than one character, it matches the character of the glyph.
         visualIndex = firstVisualCharacterIndex + index;
         break;
       }
 
-      numberOfVisualCharacters = 0u;
+      numberOfVisualCharacters = 0;
     }
-  }
+  } // for characters in visual order.
 
   // The number of characters of the whole text.
   const Length totalNumberOfCharacters = logicalModel->mText.Count();
 
   // Return the logical position of the cursor in characters.
 
-  if( !matched )
+  if( !matchedCharacter )
   {
-    // If no character is matched, then the last character (in visual order) of the line is used.
-    visualIndex = endCharacter;
+    if( isBeforeFirstGlyph )
+    {
+      // If no character is matched, then the first character (in visual order) of the line is used.
+      visualIndex = startCharacter;
+    }
+    else
+    {
+      // If no character is matched, then the last character (in visual order) of the line is used.
+      visualIndex = endCharacter;
+    }
   }
 
   // Get the paragraph direction.
@@ -264,7 +426,7 @@ CharacterIndex GetClosestCursorIndex( VisualModelPtr visualModel,
     {
       // The paragraph direction is right to left.
 
-      if( ( lineIndex != numberOfLines - 1u ) && // is not the last line.
+      if( ( lineIndex != totalNumberOfLines - 1u ) && // is not the last line.
           ( visualIndex == startCharacter ) )
       {
         // It places the cursor just after the first character in visual order.
@@ -285,7 +447,7 @@ CharacterIndex GetClosestCursorIndex( VisualModelPtr visualModel,
     // This branch checks if the closest line is the one with the last '\n'. If it is, it decrements the visual index to place
     // the cursor just before the last '\n'.
 
-    if( ( lineIndex != numberOfLines - 1u ) &&
+    if( ( lineIndex != totalNumberOfLines - 1u ) &&
         TextAbstraction::IsNewParagraph( *( logicalModel->mText.Begin() + visualIndex - 1u ) ) )
     {
       --visualIndex;
@@ -302,24 +464,26 @@ CharacterIndex GetClosestCursorIndex( VisualModelPtr visualModel,
 }
 
 
-void GetCursorPosition( VisualModelPtr visualModel,
-                        LogicalModelPtr logicalModel,
-                        MetricsPtr metrics,
-                        CharacterIndex logical,
+void GetCursorPosition( GetCursorPositionParameters& parameters,
                         CursorInfo& cursorInfo )
 {
+  const LineRun* const modelLines = parameters.visualModel->mLines.Begin();
+  if( NULL == modelLines )
+  {
+    // Nothing to do.
+    return;
+  }
+
   // Whether the logical cursor position is at the end of the whole text.
-  const bool isLastPosition = logicalModel->mText.Count() == logical;
+  const bool isLastPosition = parameters.logicalModel->mText.Count() == parameters.logical;
 
   // Get the line where the character is laid-out.
-  const CharacterIndex characterOfLine = isLastPosition ? ( logical - 1u ) : logical;
+  const CharacterIndex characterOfLine = isLastPosition ? ( parameters.logical - 1u ) : parameters.logical;
 
   // Whether the cursor is in the last position and the last position is a new paragraph character.
-  const bool isLastNewParagraph = isLastPosition && TextAbstraction::IsNewParagraph( *( logicalModel->mText.Begin() + characterOfLine ) );
-
-  const LineRun* const modelLines = visualModel->mLines.Begin();
+  const bool isLastNewParagraph = parameters.isMultiline && isLastPosition && TextAbstraction::IsNewParagraph( *( parameters.logicalModel->mText.Begin() + characterOfLine ) );
 
-  const LineIndex lineIndex = visualModel->GetLineOfCharacter( characterOfLine );
+  const LineIndex lineIndex = parameters.visualModel->GetLineOfCharacter( characterOfLine );
   const LineRun& line = *( modelLines + lineIndex );
 
   if( isLastNewParagraph )
@@ -331,7 +495,7 @@ void GetCursorPosition( VisualModelPtr visualModel,
     cursorInfo.isSecondaryCursor = false;
 
     // Set the line offset and height.
-    cursorInfo.lineOffset = CalculateLineOffset( visualModel->mLines,
+    cursorInfo.lineOffset = CalculateLineOffset( parameters.visualModel->mLines,
                                                  newLineIndex );
 
     // The line height is the addition of the line ascender and the line descender.
@@ -342,28 +506,25 @@ void GetCursorPosition( VisualModelPtr visualModel,
     cursorInfo.primaryCursorHeight = cursorInfo.lineHeight;
 
     // Set the primary cursor's position.
-    cursorInfo.primaryPosition.x = 0.f;
+    cursorInfo.primaryPosition.x = ( LTR == line.direction ) ? newLine.alignmentOffset : parameters.visualModel->mControlSize.width - newLine.alignmentOffset;
     cursorInfo.primaryPosition.y = cursorInfo.lineOffset;
-
-    // Transform the cursor info from line's coords to text's coords.
-    cursorInfo.primaryPosition.x += ( LTR == line.direction ) ? 0.f : visualModel->mControlSize.width;
   }
   else
   {
     // Whether this line is a bidirectional line.
-    const bool bidiLineFetched = logicalModel->FetchBidirectionalLineInfo( characterOfLine );
+    const bool bidiLineFetched = parameters.logicalModel->FetchBidirectionalLineInfo( characterOfLine );
 
     // Check if the logical position is the first or the last one of the line.
-    const bool isFirstPositionOfLine = line.characterRun.characterIndex == logical;
-    const bool isLastPositionOfLine = line.characterRun.characterIndex + line.characterRun.numberOfCharacters == logical;
+    const bool isFirstPositionOfLine = line.characterRun.characterIndex == parameters.logical;
+    const bool isLastPositionOfLine = line.characterRun.characterIndex + line.characterRun.numberOfCharacters == parameters.logical;
 
     // 'logical' is the logical 'cursor' index.
     // Get the next and current logical 'character' index.
-    const CharacterIndex characterIndex = isFirstPositionOfLine ? logical : logical - 1u;
-    const CharacterIndex nextCharacterIndex = isLastPositionOfLine ? characterIndex : logical;
+    const CharacterIndex characterIndex = isFirstPositionOfLine ? parameters.logical : parameters.logical - 1u;
+    const CharacterIndex nextCharacterIndex = isLastPositionOfLine ? characterIndex : parameters.logical;
 
     // The character's direction buffer.
-    const CharacterDirection* const directionsBuffer = bidiLineFetched ? logicalModel->mCharacterDirections.Begin() : NULL;
+    const CharacterDirection* const directionsBuffer = bidiLineFetched ? parameters.logicalModel->mCharacterDirections.Begin() : NULL;
 
     CharacterDirection isCurrentRightToLeft = false;
     CharacterDirection isNextRightToLeft = false;
@@ -382,7 +543,7 @@ void GetCursorPosition( VisualModelPtr visualModel,
                                      ( isFirstPositionOfLine && ( isRightToLeftParagraph != isCurrentRightToLeft ) ) );
 
     // Set the line offset and height.
-    cursorInfo.lineOffset = CalculateLineOffset( visualModel->mLines,
+    cursorInfo.lineOffset = CalculateLineOffset( parameters.visualModel->mLines,
                                                  lineIndex );
 
     // The line height is the addition of the line ascender and the line descender.
@@ -407,7 +568,7 @@ void GetCursorPosition( VisualModelPtr visualModel,
         index = isRightToLeftParagraph ? line.characterRun.characterIndex : line.characterRun.characterIndex + line.characterRun.numberOfCharacters - 1u;
         if( bidiLineFetched )
         {
-          index = logicalModel->GetLogicalCharacterIndex( index );
+          index = parameters.logicalModel->GetLogicalCharacterIndex( index );
         }
       }
       else if( isFirstPositionOfLine )
@@ -415,7 +576,7 @@ void GetCursorPosition( VisualModelPtr visualModel,
         index = isRightToLeftParagraph ? line.characterRun.characterIndex + line.characterRun.numberOfCharacters - 1u : line.characterRun.characterIndex;
         if( bidiLineFetched )
         {
-          index = logicalModel->GetLogicalCharacterIndex( index );
+          index = parameters.logicalModel->GetLogicalCharacterIndex( index );
         }
       }
       else
@@ -424,12 +585,12 @@ void GetCursorPosition( VisualModelPtr visualModel,
       }
     }
 
-    const GlyphIndex* const charactersToGlyphBuffer = visualModel->mCharactersToGlyph.Begin();
-    const Length* const glyphsPerCharacterBuffer = visualModel->mGlyphsPerCharacter.Begin();
-    const Length* const charactersPerGlyphBuffer = visualModel->mCharactersPerGlyph.Begin();
-    const CharacterIndex* const glyphsToCharactersBuffer = visualModel->mGlyphsToCharacters.Begin();
-    const Vector2* const glyphPositionsBuffer = visualModel->mGlyphPositions.Begin();
-    const GlyphInfo* const glyphInfoBuffer = visualModel->mGlyphs.Begin();
+    const GlyphIndex* const charactersToGlyphBuffer = parameters.visualModel->mCharactersToGlyph.Begin();
+    const Length* const glyphsPerCharacterBuffer = parameters.visualModel->mGlyphsPerCharacter.Begin();
+    const Length* const charactersPerGlyphBuffer = parameters.visualModel->mCharactersPerGlyph.Begin();
+    const CharacterIndex* const glyphsToCharactersBuffer = parameters.visualModel->mGlyphsToCharacters.Begin();
+    const Vector2* const glyphPositionsBuffer = parameters.visualModel->mGlyphPositions.Begin();
+    const GlyphInfo* const glyphInfoBuffer = parameters.visualModel->mGlyphs.Begin();
 
     // Convert the cursor position into the glyph position.
     const GlyphIndex primaryGlyphIndex = *( charactersToGlyphBuffer + index );
@@ -442,7 +603,7 @@ void GetCursorPosition( VisualModelPtr visualModel,
                       primaryNumberOfGlyphs,
                       glyphMetrics,
                       glyphInfoBuffer,
-                      metrics );
+                      parameters.metrics );
 
     // Whether to add the glyph's advance to the cursor position.
     // i.e if the paragraph is left to right and the logical cursor is zero, the position is the position of the first glyph and the advance is not added,
@@ -491,7 +652,7 @@ void GetCursorPosition( VisualModelPtr visualModel,
         isCurrentRightToLeft = *( directionsBuffer + index );
       }
 
-      Length numberOfGlyphAdvance = ( isFirstPositionOfLine ? 0u : 1u ) + characterIndex - firstIndex;
+      Length numberOfGlyphAdvance = ( isFirstPositionOfLine ? 0 : 1u ) + characterIndex - firstIndex;
       if( isCurrentRightToLeft )
       {
         numberOfGlyphAdvance = primaryNumberOfCharacters - numberOfGlyphAdvance;
@@ -506,9 +667,10 @@ void GetCursorPosition( VisualModelPtr visualModel,
     // Set the primary cursor's height.
     cursorInfo.primaryCursorHeight = cursorInfo.isSecondaryCursor ? 0.5f * glyphMetrics.fontHeight : glyphMetrics.fontHeight;
 
+    cursorInfo.glyphOffset = line.ascender - glyphMetrics.ascender;
     // Set the primary cursor's position.
     cursorInfo.primaryPosition.x = -glyphMetrics.xBearing + primaryPosition.x + glyphAdvance;
-    cursorInfo.primaryPosition.y = cursorInfo.lineOffset + line.ascender - glyphMetrics.ascender;
+    cursorInfo.primaryPosition.y = cursorInfo.lineOffset + cursorInfo.glyphOffset;
 
     // Transform the cursor info from line's coords to text's coords.
     cursorInfo.primaryPosition.x += line.alignmentOffset;
@@ -534,7 +696,7 @@ void GetCursorPosition( VisualModelPtr visualModel,
                         secondaryNumberOfGlyphs,
                         glyphMetrics,
                         glyphInfoBuffer,
-                        metrics );
+                        parameters.metrics );
 
       // Set the secondary cursor's position.
 
@@ -566,60 +728,140 @@ void GetCursorPosition( VisualModelPtr visualModel,
   }
 }
 
-void FindSelectionIndices( VisualModelPtr visualModel,
+bool FindSelectionIndices( VisualModelPtr visualModel,
                            LogicalModelPtr logicalModel,
                            MetricsPtr metrics,
                            float visualX,
                            float visualY,
                            CharacterIndex& startIndex,
-                           CharacterIndex& endIndex )
+                           CharacterIndex& endIndex,
+                           CharacterIndex& noTextHitIndex )
 {
+/*
+  Hit character                                           Select
+|-------------------------------------------------------|------------------------------------------|
+| On a word                                             | The word                                 |
+| On a single white space between words                 | The word before or after the white space |
+| On one of the multiple contiguous white spaces        | The white spaces                         |
+| On a single white space which is in the position zero | The white space and the next word        |
+| On a new paragraph character                          | The word or group of white spaces before |
+|-------------------------------------------------------|------------------------------------------|
+*/
+  const Length totalNumberOfCharacters = logicalModel->mText.Count();
+  startIndex = 0;
+  endIndex = 0;
+  noTextHitIndex = 0;
+
+  if( 0 == totalNumberOfCharacters )
+  {
+    // Nothing to do if the model is empty.
+    return false;
+  }
+
+  bool matchedCharacter = false;
   CharacterIndex hitCharacter = Text::GetClosestCursorIndex( visualModel,
                                                              logicalModel,
                                                              metrics,
                                                              visualX,
-                                                             visualY );
-  DALI_ASSERT_DEBUG( hitCharacter <= logicalModel->mText.Count() && "GetClosestCursorIndex returned out of bounds index" );
+                                                             visualY,
+                                                             CharacterHitTest::TAP,
+                                                             matchedCharacter );
 
-  if( logicalModel->mText.Count() == 0 )
+  if( !matchedCharacter )
   {
-    return;  // if model empty
+    noTextHitIndex = hitCharacter;
   }
 
-  if( hitCharacter >= logicalModel->mText.Count() )
+  DALI_ASSERT_DEBUG( ( hitCharacter <= totalNumberOfCharacters ) && "GetClosestCursorIndex returned out of bounds index" );
+
+  if( hitCharacter >= totalNumberOfCharacters )
   {
     // Closest hit character is the last character.
-    if( hitCharacter ==  logicalModel->mText.Count() )
+    if( hitCharacter == totalNumberOfCharacters )
     {
       hitCharacter--; //Hit character index set to last character in logical model
     }
     else
     {
       // hitCharacter is out of bounds
-      return;
+      return false;
     }
   }
 
+  const Character* const textBuffer = logicalModel->mText.Begin();
+
   startIndex = hitCharacter;
   endIndex = hitCharacter;
-  bool isHitCharacterWhitespace = TextAbstraction::IsWhiteSpace( logicalModel->mText[hitCharacter] );
 
-  // Find the start and end of the text
-  for( startIndex = hitCharacter; startIndex > 0; --startIndex )
+  // Whether the hit character is a new paragraph character.
+  const bool isHitCharacterNewParagraph = TextAbstraction::IsNewParagraph( *( textBuffer + hitCharacter ) );
+
+  // Whether the hit character is a white space. Note a new paragraph character is a white space as well but here is not wanted.
+  const bool isHitCharacterWhiteSpace = TextAbstraction::IsWhiteSpace( *( textBuffer + hitCharacter ) ) && !isHitCharacterNewParagraph;
+
+  FindWordData data( textBuffer,
+                     totalNumberOfCharacters,
+                     hitCharacter,
+                     isHitCharacterWhiteSpace,
+                     isHitCharacterNewParagraph );
+
+  if( isHitCharacterNewParagraph )
   {
-    if( isHitCharacterWhitespace != TextAbstraction::IsWhiteSpace( logicalModel->mText[ startIndex-1 ] ) )
+    // Find the first character before the hit one which is not a new paragraph character.
+
+    if( hitCharacter > 0 )
     {
-      break;
+      endIndex = hitCharacter - 1u;
+      for( ; endIndex > 0; --endIndex )
+      {
+        const Dali::Toolkit::Text::Character character = *( data.textBuffer + endIndex );
+
+        if( !Dali::TextAbstraction::IsNewParagraph( character ) )
+        {
+          break;
+        }
+      }
     }
+
+    data.hitCharacter = endIndex;
+    data.isNewParagraph = false;
+    data.isWhiteSpace = TextAbstraction::IsWhiteSpace( *( textBuffer + data.hitCharacter ) );
   }
-  const CharacterIndex pastTheEnd = logicalModel->mText.Count();
-  for( endIndex = hitCharacter + 1u; endIndex < pastTheEnd; ++endIndex )
+
+  // Find the start of the word.
+  FindStartOfWord( data );
+  startIndex = data.foundIndex;
+
+  // Find the end of the word.
+  FindEndOfWord( data );
+  endIndex = data.foundIndex;
+
+  if( 1u == ( endIndex - startIndex ) )
   {
-    if( isHitCharacterWhitespace != TextAbstraction::IsWhiteSpace( logicalModel->mText[ endIndex ] ) )
+    if( isHitCharacterWhiteSpace )
     {
-      break;
+      // Select the word before or after the white space
+
+      if( 0 == hitCharacter )
+      {
+        data.isWhiteSpace = false;
+        FindEndOfWord( data );
+        endIndex = data.foundIndex;
+      }
+      else if( hitCharacter > 0 )
+      {
+        // Find the start of the word.
+        data.hitCharacter = hitCharacter - 1u;
+        data.isWhiteSpace = false;
+        FindStartOfWord( data );
+        startIndex = data.foundIndex;
+
+        --endIndex;
+      }
     }
   }
+
+  return matchedCharacter;
 }
 
 } // namespace Text