Multi-line text. Cursor hit and cursor's position. 29/71929/4
authorVictor Cebollada <v.cebollada@samsung.com>
Wed, 4 May 2016 13:38:12 +0000 (14:38 +0100)
committerVictor Cebollada <v.cebollada@samsung.com>
Tue, 31 May 2016 13:45:31 +0000 (14:45 +0100)
Change-Id: I28f664ea7d1dfed61fcd4e69a3de8ec850b4df58
Signed-off-by: Victor Cebollada <v.cebollada@samsung.com>
12 files changed:
automated-tests/src/dali-toolkit-internal/CMakeLists.txt
automated-tests/src/dali-toolkit-internal/dali-toolkit-test-utils/toolkit-text-model.cpp
automated-tests/src/dali-toolkit-internal/dali-toolkit-test-utils/toolkit-text-model.h
automated-tests/src/dali-toolkit-internal/utc-Dali-BidirectionalSupport.cpp
automated-tests/src/dali-toolkit-internal/utc-Dali-LogicalModel.cpp
automated-tests/src/dali-toolkit-internal/utc-Dali-Text-Cursor.cpp [new file with mode: 0644]
automated-tests/src/dali-toolkit-internal/utc-Dali-Text-Layout.cpp
automated-tests/src/dali-toolkit-internal/utc-Dali-Text-Shaping.cpp
automated-tests/src/dali-toolkit-internal/utc-Dali-VisualModel.cpp
dali-toolkit/internal/text/cursor-helper-functions.cpp
dali-toolkit/internal/text/cursor-helper-functions.h
dali-toolkit/internal/text/layouts/layout-engine.cpp

index b2a4e29..b23014b 100644 (file)
@@ -9,6 +9,7 @@ SET(CAPI_LIB "dali-toolkit-internal")
 SET(TC_SOURCES
  utc-Dali-PushButton.cpp
  utc-Dali-Text-CharacterSetConversion.cpp
+ utc-Dali-Text-Cursor.cpp
  utc-Dali-Text-Segmentation.cpp
  utc-Dali-Text-MultiLanguage.cpp
  utc-Dali-LogicalModel.cpp
index 83fa45c..21d9f42 100644 (file)
@@ -25,7 +25,6 @@
 #include <dali-toolkit/internal/text/character-set-conversion.h>
 #include <dali-toolkit/internal/text/layouts/layout-engine.h>
 #include <dali-toolkit/internal/text/layouts/layout-parameters.h>
-#include <dali-toolkit/internal/text/metrics.h>
 #include <dali-toolkit/internal/text/multi-language-support.h>
 #include <dali-toolkit/internal/text/segmentation.h>
 #include <dali-toolkit/internal/text/shaper.h>
@@ -97,9 +96,13 @@ void CreateTextModel( const std::string& text,
                       const Vector<FontDescriptionRun>& fontDescriptions,
                       const LayoutOptions& options,
                       Size& layoutSize,
-                      LogicalModelPtr logicalModel,
-                      VisualModelPtr visualModel )
+                      LogicalModelPtr& logicalModel,
+                      VisualModelPtr& visualModel,
+                      MetricsPtr& metrics )
 {
+  logicalModel = LogicalModel::New();
+  visualModel = VisualModel::New();
+
   // 1) Convert to utf32
   Vector<Character>& utf32Characters = logicalModel->mText;
   utf32Characters.Resize( text.size() );
@@ -242,7 +245,7 @@ void CreateTextModel( const std::string& text,
   const Length numberOfGlyphs = glyphs.Count();
 
   // 8) Get the glyph metrics
-  MetricsPtr metrics = Metrics::New( fontClient );
+  metrics = Metrics::New( fontClient );
 
   GlyphInfo* glyphsBuffer = glyphs.Begin();
   metrics->GetGlyphMetrics( glyphsBuffer, numberOfGlyphs );
index 631b151..288627f 100644 (file)
@@ -22,6 +22,7 @@
 
 // INTERNAL INCLUDES
 #include <dali-toolkit/internal/text/logical-model-impl.h>
+#include <dali-toolkit/internal/text/metrics.h>
 #include <dali-toolkit/internal/text/visual-model-impl.h>
 
 namespace Dali
@@ -58,14 +59,16 @@ struct LayoutOptions
  * @param[out] layoutSize The laid-out size.
  * @param[out] logicalModel Pointer to a logical text model instance.
  * @param[out] visualModel Pointer to a visual text model instance.
+ * @param[out] metrics Pointer to a wrapper around FontClient used to get metrics.
  */
 void CreateTextModel( const std::string& text,
                       const Size& textArea,
                       const Vector<FontDescriptionRun>& fontDescriptions,
                       const LayoutOptions& options,
                       Size& layoutSize,
-                      LogicalModelPtr logicalModel,
-                      VisualModelPtr visualModel );
+                      LogicalModelPtr& logicalModel,
+                      VisualModelPtr& visualModel,
+                      MetricsPtr& metrics );
 
 } // namespace Text
 
index f61e539..629e4f3 100644 (file)
@@ -111,8 +111,9 @@ struct GetCharactersDirectionData
 bool SetBidirectionalInfoTest( const SetBidirectionalInfoData& data )
 {
   // 1) Create the model.
-  LogicalModelPtr logicalModel = LogicalModel::New();
-  VisualModelPtr visualModel = VisualModel::New();
+  LogicalModelPtr logicalModel;
+  VisualModelPtr visualModel;
+  MetricsPtr metrics;
   Size textArea(100.f, 60.f);
   Size layoutSize;
 
@@ -125,7 +126,8 @@ bool SetBidirectionalInfoTest( const SetBidirectionalInfoData& data )
                    options,
                    layoutSize,
                    logicalModel,
-                   visualModel );
+                   visualModel,
+                   metrics );
 
   // 2) Clear the bidirectional paragraph info data.
   Vector<BidirectionalParagraphInfoRun>& bidirectionalInfo = logicalModel->mBidirectionalParagraphInfo;
@@ -215,8 +217,9 @@ void FreeBidirectionalLineInfoResources( Vector<BidirectionalLineInfoRun> bidire
 bool ReorderLinesTest( const ReorderLinesData& data )
 {
   // 1) Create the model.
-  LogicalModelPtr logicalModel = LogicalModel::New();
-  VisualModelPtr visualModel = VisualModel::New();
+  LogicalModelPtr logicalModel;
+  VisualModelPtr visualModel;
+  MetricsPtr metrics;
   Size textArea(100.f, 300.f);
   Size layoutSize;
 
@@ -229,7 +232,8 @@ bool ReorderLinesTest( const ReorderLinesData& data )
                    options,
                    layoutSize,
                    logicalModel,
-                   visualModel );
+                   visualModel,
+                   metrics );
 
   // 2) Clear the bidirectional line info data.
   uint32_t startRemoveIndex = logicalModel->mBidirectionalLineInfo.Count();
@@ -338,8 +342,9 @@ bool ReorderLinesTest( const ReorderLinesData& data )
 bool GetMirroredTextTest( const GetMirroredTextData& data )
 {
   // 1) Create the model.
-  LogicalModelPtr logicalModel = LogicalModel::New();
-  VisualModelPtr visualModel = VisualModel::New();
+  LogicalModelPtr logicalModel;
+  VisualModelPtr visualModel;
+  MetricsPtr metrics;
   Size textArea(100.f, 60.f);
   Size layoutSize;
 
@@ -352,7 +357,8 @@ bool GetMirroredTextTest( const GetMirroredTextData& data )
                    options,
                    layoutSize,
                    logicalModel,
-                   visualModel );
+                   visualModel,
+                   metrics );
 
   // 2) Call the GetMirroredText() function for the whole text
   Vector<Character> mirroredText;
@@ -410,8 +416,9 @@ bool GetMirroredTextTest( const GetMirroredTextData& data )
 bool GetCharactersDirectionTest( const GetCharactersDirectionData& data )
 {
   // 1) Create the model.
-  LogicalModelPtr logicalModel = LogicalModel::New();
-  VisualModelPtr visualModel = VisualModel::New();
+  LogicalModelPtr logicalModel;
+  VisualModelPtr visualModel;
+  MetricsPtr metrics;
   Size textArea(100.f, 60.f);
   Size layoutSize;
 
@@ -424,7 +431,8 @@ bool GetCharactersDirectionTest( const GetCharactersDirectionData& data )
                    options,
                    layoutSize,
                    logicalModel,
-                   visualModel );
+                   visualModel,
+                   metrics );
 
   Vector<BidirectionalParagraphInfoRun>& bidirectionalInfo = logicalModel->mBidirectionalParagraphInfo;
 
@@ -459,7 +467,6 @@ bool GetCharactersDirectionTest( const GetCharactersDirectionData& data )
 
 int UtcDaliSetBidirectionalInfo(void)
 {
-  ToolkitTestApplication application;
   tet_infoline(" UtcDaliSetBidirectionalInfo");
 
   unsigned int indices01[] = {};
@@ -594,6 +601,7 @@ int UtcDaliSetBidirectionalInfo(void)
 
   for( unsigned int index = 0u; index < numberOfTests; ++index )
   {
+    ToolkitTestApplication application;
     if( !SetBidirectionalInfoTest( data[index] ) )
     {
       tet_result(TET_FAIL);
@@ -606,7 +614,6 @@ int UtcDaliSetBidirectionalInfo(void)
 
 int UtcDaliReorderLines(void)
 {
-  ToolkitTestApplication application;
   tet_infoline(" UtcDaliSetBidirectionalInfo");
 
   unsigned int visualToLogical0301[] = { 0u, 1u, 2u, 3u, 4u, 5u, 9u, 8u, 7u, 6u, 10u };
@@ -781,6 +788,7 @@ int UtcDaliReorderLines(void)
 
   for( unsigned int index = 0u; index < numberOfTests; ++index )
   {
+    ToolkitTestApplication application;
     if( !ReorderLinesTest( data[index] ) )
     {
       tet_result(TET_FAIL);
@@ -793,7 +801,6 @@ int UtcDaliReorderLines(void)
 
 int UtcDaliGetMirroredText(void)
 {
-  ToolkitTestApplication application;
   tet_infoline(" UtcDaliGetMirroredText");
 
   struct GetMirroredTextData data[] =
@@ -865,6 +872,7 @@ int UtcDaliGetMirroredText(void)
 
   for( unsigned int index = 0u; index < numberOfTests; ++index )
   {
+    ToolkitTestApplication application;
     if( !GetMirroredTextTest( data[index] ) )
     {
       tet_result(TET_FAIL);
@@ -877,7 +885,6 @@ int UtcDaliGetMirroredText(void)
 
 int UtcDaliGetCharactersDirection(void)
 {
-  ToolkitTestApplication application;
   tet_infoline(" UtcDaliGetCharactersDirection");
 
   bool directions01[] = {};
@@ -987,6 +994,7 @@ int UtcDaliGetCharactersDirection(void)
 
   for( unsigned int index = 0u; index < numberOfTests; ++index )
   {
+    ToolkitTestApplication application;
     if( !GetCharactersDirectionTest( data[index] ) )
     {
       tet_result(TET_FAIL);
index 9fe39c0..25a724c 100644 (file)
@@ -100,8 +100,9 @@ struct GetLogicalCursorIndexData
 bool CreateParagraphTest( const CreateParagraphData& data )
 {
   // 1) Create the model.
-  LogicalModelPtr logicalModel = LogicalModel::New();
-  VisualModelPtr visualModel = VisualModel::New();
+  LogicalModelPtr logicalModel;
+  VisualModelPtr visualModel;
+  MetricsPtr metrics;
   Size textArea(100.f, 60.f);
   Size layoutSize;
 
@@ -113,7 +114,8 @@ bool CreateParagraphTest( const CreateParagraphData& data )
                    options,
                    layoutSize,
                    logicalModel,
-                   visualModel );
+                   visualModel,
+                   metrics );
 
   // 2) Clear the paragraphs.
   Vector<ParagraphRun>& paragraphs = logicalModel->mParagraphInfo;
@@ -158,8 +160,9 @@ bool CreateParagraphTest( const CreateParagraphData& data )
 bool FindParagraphTest( const FindParagraphData& data )
 {
   // 1) Create the model.
-  LogicalModelPtr logicalModel = LogicalModel::New();
-  VisualModelPtr visualModel = VisualModel::New();
+  LogicalModelPtr logicalModel;
+  VisualModelPtr visualModel;
+  MetricsPtr metrics;
   Size textArea(100.f, 60.f);
   Size layoutSize;
 
@@ -171,7 +174,8 @@ bool FindParagraphTest( const FindParagraphData& data )
                    options,
                    layoutSize,
                    logicalModel,
-                   visualModel );
+                   visualModel,
+                   metrics );
 
   // 2) Find the paragraphs.
   Vector<ParagraphRunIndex> paragraphs;
@@ -204,8 +208,9 @@ bool FetchBidirectionalLineInfoTest( const FetchBidirectionalLineInfoData& data
 {
   std::cout << "  testing : " << data.description << std::endl;
   // Create the model.
-  LogicalModelPtr logicalModel = LogicalModel::New();
-  VisualModelPtr visualModel = VisualModel::New();
+  LogicalModelPtr logicalModel;
+  VisualModelPtr visualModel;
+  MetricsPtr metrics;
   Size textArea( 100.f, 300.f );
   Size layoutSize;
 
@@ -218,7 +223,8 @@ bool FetchBidirectionalLineInfoTest( const FetchBidirectionalLineInfoData& data
                    options,
                    layoutSize,
                    logicalModel,
-                   visualModel );
+                   visualModel,
+                   metrics );
 
   for( unsigned int index = 0; index < data.numberOfTests; ++index )
   {
@@ -247,8 +253,9 @@ bool GetLogicalCharacterIndexTest( const GetLogicalCharacterIndexData& data )
 {
   std::cout << "  testing : " << data.description << std::endl;
   // Create the model.
-  LogicalModelPtr logicalModel = LogicalModel::New();
-  VisualModelPtr visualModel = VisualModel::New();
+  LogicalModelPtr logicalModel;
+  VisualModelPtr visualModel;
+  MetricsPtr metrics;
   Size layoutSize;
 
   // Create the model with the whole text.
@@ -260,7 +267,8 @@ bool GetLogicalCharacterIndexTest( const GetLogicalCharacterIndexData& data )
                    options,
                    layoutSize,
                    logicalModel,
-                   visualModel );
+                   visualModel,
+                   metrics );
 
   for( unsigned int index = 0u; index < data.numberOfIndices; ++index )
   {
@@ -287,8 +295,9 @@ bool GetLogicalCursorIndexTest( const GetLogicalCursorIndexData& data )
 {
   std::cout << "  testing : " << data.description << std::endl;
   // Create the model.
-  LogicalModelPtr logicalModel = LogicalModel::New();
-  VisualModelPtr visualModel = VisualModel::New();
+  LogicalModelPtr logicalModel;
+  VisualModelPtr visualModel;
+  MetricsPtr metrics;
   Size layoutSize;
 
   // Create the model with the whole text.
@@ -300,7 +309,8 @@ bool GetLogicalCursorIndexTest( const GetLogicalCursorIndexData& data )
                    options,
                    layoutSize,
                    logicalModel,
-                   visualModel );
+                   visualModel,
+                   metrics );
 
   for( unsigned int index = 0u; index < data.numberOfIndices; ++index )
   {
diff --git a/automated-tests/src/dali-toolkit-internal/utc-Dali-Text-Cursor.cpp b/automated-tests/src/dali-toolkit-internal/utc-Dali-Text-Cursor.cpp
new file mode 100644 (file)
index 0000000..195c81b
--- /dev/null
@@ -0,0 +1,381 @@
+/*
+ * Copyright (c) 2016 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.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#include <iostream>
+
+#include <stdlib.h>
+
+#include <dali-toolkit-test-suite-utils.h>
+#include <dali-toolkit/internal/text/cursor-helper-functions.h>
+#include <dali-toolkit/dali-toolkit.h>
+#include <toolkit-text-model.h>
+
+
+using namespace Dali;
+using namespace Toolkit;
+using namespace Text;
+
+// Tests the following functions.
+//
+// LineIndex GetClosestLine( VisualModelPtr visualModel,
+//                           float visualY )
+// CharacterIndex GetClosestCursorIndex( VisualModelPtr visualModel,
+//                                       LogicalModelPtr logicalModel,
+//                                       MetricsPtr metrics,
+//                                       float visualX,
+//                                       float visualY )
+
+//////////////////////////////////////////////////////////
+
+namespace
+{
+
+struct GetClosestLineData
+{
+  std::string    description;                    ///< Description of the test.
+  std::string    text;                           ///< Input text.
+  unsigned int   numberOfTests;                  ///< The number of tests.
+  float*         visualY;                        ///< The visual 'y' position for each test.
+  LineIndex*     lineIndex;                      ///< The expected line index for each test.
+};
+
+struct GetClosestCursorIndexData
+{
+  std::string     description;                    ///< Description of the test.
+  std::string     text;                           ///< Input text.
+  unsigned int    numberOfTests;                  ///< The number of tests.
+  float*          visualX;                        ///< The visual 'x' position for each test.
+  float*          visualY;                        ///< The visual 'y' position for each test.
+  CharacterIndex* logicalIndex;                   ///< The expected logical cursor index for each test.
+};
+
+bool GetClosestLineTest( const GetClosestLineData& data )
+{
+  std::cout << "  testing : " << data.description << std::endl;
+
+  // 1) Create the model.
+  LogicalModelPtr logicalModel;
+  VisualModelPtr visualModel;
+  MetricsPtr metrics;
+  Size textArea(400.f, 600.f);
+  Size layoutSize;
+
+  Vector<FontDescriptionRun> fontDescriptionRuns;
+  LayoutOptions options;
+  CreateTextModel( data.text,
+                   textArea,
+                   fontDescriptionRuns,
+                   options,
+                   layoutSize,
+                   logicalModel,
+                   visualModel,
+                   metrics );
+
+  for( unsigned int index = 0u; index < data.numberOfTests; ++index )
+  {
+    const LineIndex lineIndex = GetClosestLine( visualModel,
+                                                data.visualY[index] );
+
+    if( lineIndex != data.lineIndex[index] )
+    {
+      std::cout << "  test " << index << " failed. Different line index : " << lineIndex << ", expected : " << data.lineIndex[index] << std::endl;
+      return false;
+    }
+  }
+
+  return true;
+}
+
+bool GetClosestCursorIndexTest( const GetClosestCursorIndexData& data )
+{
+  std::cout << "  testing : " << data.description << std::endl;
+
+  // 1) Create the model.
+  LogicalModelPtr logicalModel;
+  VisualModelPtr visualModel;
+  MetricsPtr metrics;
+  Size textArea(400.f, 600.f);
+  Size layoutSize;
+
+  Vector<FontDescriptionRun> fontDescriptionRuns;
+  LayoutOptions options;
+  CreateTextModel( data.text,
+                   textArea,
+                   fontDescriptionRuns,
+                   options,
+                   layoutSize,
+                   logicalModel,
+                   visualModel,
+                   metrics );
+
+  for( unsigned int index = 0u; index < data.numberOfTests; ++index )
+  {
+    const CharacterIndex logicalCursorIndex = GetClosestCursorIndex( visualModel,
+                                                                     logicalModel,
+                                                                     metrics,
+                                                                     data.visualX[index],
+                                                                     data.visualY[index] );
+
+    if( logicalCursorIndex != data.logicalIndex[index] )
+    {
+      std::cout << "  test " << index << " failed. Different logical cursor index : " << logicalCursorIndex << ", expected : " << data.logicalIndex[index] << std::endl;
+      return false;
+    }
+  }
+
+  return true;
+}
+
+} // namespace
+
+//////////////////////////////////////////////////////////
+//
+// UtcDaliGetClosestLine
+// UtcDaliGetClosestCursorIndex
+//
+//////////////////////////////////////////////////////////
+
+int UtcDaliGetClosestLine(void)
+{
+  tet_infoline(" UtcDaliGetClosestLine");
+
+  float visualY01[] = { -4.f, 3.f, 1000.f };
+  LineIndex lineIndices01[] = { 0u, 0u, 0u };
+
+  float visualY02[] = { -4.f, 3.f, 1000.f };
+  LineIndex lineIndices02[] = { 0u, 0u, 0u };
+
+  float visualY03[] = { -4.f, 11.f, 30.f, 51.f, 68.f, 87.f, 109.f, 130.f };
+  LineIndex lineIndices03[] = { 0u, 0u, 1u, 2u, 3u, 4u, 5u, 5u };
+
+  struct GetClosestLineData data[] =
+  {
+    {
+      "void text.",
+      "",
+      3u,
+      visualY01,
+      lineIndices01,
+    },
+    {
+      "Single line text.",
+      "hello world",
+      3u,
+      visualY02,
+      lineIndices02,
+    },
+    {
+      "Multi-line text.",
+      "abcשנבdefגקכghiעיןjklחלךmnoצמםpqrפרףstuדאוvwxה"
+      "סתyzטזץabcשנבdefגקכghiעיןjklחלךmnoצמםpqrפרףstuד"
+      "אוvwxהסתyzטזץabcשנבdefגקכghiעיןjklחלךmnoצמםpqr"
+      "פרףstuדאוvwxהסתyzטזץabcשנבdefגקכghiעיןjklחלךmno"
+      "צמםpqrפרףstuדאוvwxהסתyzטזץabcשנבdefגקכghiעיןjkl"
+      "חלךmnoצמםpqrפרףstuדאוvwxהסתyzטזץ",
+      8u,
+      visualY03,
+      lineIndices03
+    }
+  };
+  const unsigned int numberOfTests = 3u;
+
+  for( unsigned int index = 0u; index < numberOfTests; ++index )
+  {
+    ToolkitTestApplication application;
+    if( !GetClosestLineTest( data[index] ) )
+    {
+      tet_result(TET_FAIL);
+    }
+  }
+
+  tet_result(TET_PASS);
+  END_TEST;
+}
+
+int UtcDaliGetClosestCursorIndex(void)
+{
+  tet_infoline(" UtcDaliGetClosestCursorIndex");
+  float visualX01[] = { -100.f };
+  float visualY01[] = { -100.f };
+  CharacterIndex logicalIndex01[] = { 0u };
+
+  float visualX02[] = { -100.f, 1000.f, 60.f, 79.f, 83.f, 148.f, 99.f };
+  float visualY02[] = { -100.f, 1000.f, 12.f, 12.f, 12.f, 12.f, 12.f };
+  CharacterIndex logicalIndex02[] = { 0u, 21u, 7u, 10u, 11u, 13u, 20u };
+
+  float visualX03[] = { 19.f, 104.f, -2.f, 127.f };
+  float visualY03[] = { 12.f, 12.f, 12.f, 12.f };
+  CharacterIndex logicalIndex03[] = { 3u, 12u, 0u, 18u };
+
+  //  0     5 _ 6     11  12
+  //   Hello     world  \n
+  // 12    16 _ 17    21   22
+  //   שלום       עולם  \n
+  // 22         31_32      40  41
+  //   different     الأربعاء  \n
+  float visualX04[] = { -100.f, 40.f, 44.f, 85.f, 500.f,
+                         500.f, 367.f, 359.f, 329.f, -100.f,
+                        -100.f, 19.f, 64.f, 72.f, 104.f, 111.f, 500.f};
+  float visualY04[] = { -100.f, 12.f, 12.f, 12.f, 12.f,
+                          30.f, 30.f, 30.f, 30.f, 30.f,
+                          50.f, 50.f, 50.f, 50.f, 50.f, 50.f, 50.f };
+  CharacterIndex logicalIndex04[] = {  0u,  5u,  6u, 11u, 11u,
+                                       12u, 16u, 17u, 21u, 21u,
+                                       22u, 25u, 31u, 32u, 34u, 40u, 40u,
+                                       41u };
+
+  //   0           10           20            30           40      46
+  //    abcשנבdefג   קכghiעיןjk   lחלךmnoצמם   pqrפרףstuד   אוvwxה
+  //  46     50            60            70           80               93
+  //    סתyz   טזץabcשנבd    efגקכghiעי    ןjklחלךmno   צמםpqrפרףstuד
+  //  93       100           110          120         130          139
+  //    אוvwxהס   תyzטזץabcש   נבdefגקכgh   iעיןjklחלך   mnoצמםpqr
+  // 139           150           160           170          180       186
+  //    פרףstuדאוvw   xהסתyzטזץa   bcשנבdefגק    כghiעיןjkl    חלךmno
+  // 186     190           200           210          220            233
+  //    צמםp   qrפרףstuדא    וvwxהסתyzט   זץabcשנבde   fגקכghiעיןjkl
+  // 233        240            250           260     265
+  //    חלךmnoצ    מםpqrפרףst   uדאוvwxהסת    yzטזץ
+
+  float visualX05[] = { -100.f, 96.f, 155.f, 250.f, 344.f, 500.f,
+                        -100.f, 36.f, 124.f, 190.f, 280.f, 500.f,
+                        -100.f, 56.f, 158.f, 237.f, 303.f, 500.f,
+                        -100.f, 98.f, 184.f, 261.f, 337.f, 500.f,
+                        -100.f, 40.f, 113.f, 223.f, 302.f, 500.f,
+                        -100.f, 82.f, 160.f, 253.f, 500.f };
+  float visualY05[] = { -100.f, 12.f, 12.f, 12.f, 12.f, 12.f,
+                        30.f, 30.f, 30.f, 30.f, 30.f, 30.f,
+                        50.f, 50.f, 50.f, 50.f, 50.f, 50.f,
+                        67.f, 67.f, 67.f, 67.f, 67.f, 67.f,
+                        87.f, 87.f, 87.f, 87.f, 87.f, 87.f,
+                        107.f, 107.f, 107.f, 107.f, 107.f };
+  CharacterIndex logicalIndex05[] = {   0u,  10u,  20u,  30u,  40u,  45u,
+                                       46u,  50u,  60u,  70u,  80u,  92u,
+                                       93u, 100u, 110u, 120u, 130u, 138u,
+                                      139u, 150u, 160u, 170u, 180u, 185u,
+                                      186u, 190u, 200u, 210u, 220u, 232u,
+                                      233u, 240u, 250u, 260u, 265u };
+
+  //   0            10           20           30           40        46
+  //    שנבabcגקכd    efעיןghiחל   ךjklצמםmno   פרףpqrדאוs   tuהסתv
+  //  46     50           60          70            80              93
+  //    wxטז   ץyzשנבabcג   קכdefעיןgh   iחלךjklצמם   mnoפרףpqrדאוs
+  //  93        100          110          120           130           139
+  //    tuהסתvw   xטזץyzשנבa   bcגקכdefעי    ןghiחלךjkl    צמםmnoפרף
+  // 139           150           160          170         180       186
+  //    pqrדאוstuהס   תvwxטזץyzש   נבabcגקכde   fעיןghiחלך   jklצמם
+  // 186    190          200           210           220            232
+  //    mnoפ   רףpqrדאוst   uהסתvwxטזץ   yzשנבabcגק    כdefעיןghiחל
+  // 232         240           250           260     265
+  //    ךjklצמםm   noפרףpqrדא    וstuהסתvwx   טזץyz
+
+  float visualX06[] = { 500.f, 307.f, 237.f, 148.f, 55.f, -100.f,
+                        500.f, 362.f, 276.f, 213.f, 121.f, -100.f,
+                        500.f, 344.f, 238.f, 167.f, 93.f, -100.f,
+                        500.f, 306.f, 216.f, 142.f, 58.f, -100.f,
+                        500.f, 355.f, 279.f, 182.f, 92.f, -100.f,
+                        500.f, 326.f, 238.f, 150.f, -100.f };
+  float visualY06[] = { -100.f, 12.f, 12.f, 12.f, 12.f, 12.f,
+                        30.f, 30.f, 30.f, 30.f, 30.f, 30.f,
+                        50.f, 50.f, 50.f, 50.f, 50.f, 50.f,
+                        67.f, 67.f, 67.f, 67.f, 67.f, 67.f,
+                        87.f, 87.f, 87.f, 87.f, 87.f, 87.f,
+                        107.f, 107.f, 107.f, 107.f, 107.f };
+  CharacterIndex logicalIndex06[] = {   0u,  10u,  20u,  30u,  40u,  45u,
+                                       46u,  50u,  60u,  70u,  80u,  92u,
+                                       93u, 100u, 110u, 120u, 130u, 138u,
+                                      139u, 150u, 160u, 170u, 180u, 185u,
+                                      186u, 190u, 200u, 210u, 220u, 231u,
+                                      232u, 240u, 250u, 260u, 265u  };
+
+  struct GetClosestCursorIndexData data[] =
+  {
+    {
+      "Void text.",
+      "",
+      1u,
+      visualX01,
+      visualY01,
+      logicalIndex01
+    },
+    {
+      "Single line text.",
+      "Hello world שלום עולם",
+      7u,
+      visualX02,
+      visualY02,
+      logicalIndex02
+    },
+    {
+      "Single line with ligatures",
+      "different الأربعاء",
+      4u,
+      visualX03,
+      visualY03,
+      logicalIndex03
+    },
+    {
+      "Multiline. Single line paragraphs",
+      "Hello world\n"
+      "שלום עולם\n"
+      "different الأربعاء\n",
+      17u,
+      visualX04,
+      visualY04,
+      logicalIndex04
+    },
+    {
+      "Multiline. Single bidirectional paragraph, starts LTR, wrapped lines",
+      "abcשנבdefגקכghiעיןjklחלךmnoצמםpqrפרףstuדאוvwxה"
+      "סתyzטזץabcשנבdefגקכghiעיןjklחלךmnoצמםpqrפרףstuד"
+      "אוvwxהסתyzטזץabcשנבdefגקכghiעיןjklחלךmnoצמםpqr"
+      "פרףstuדאוvwxהסתyzטזץabcשנבdefגקכghiעיןjklחלךmno"
+      "צמםpqrפרףstuדאוvwxהסתyzטזץabcשנבdefגקכghiעיןjkl"
+      "חלךmnoצמםpqrפרףstuדאוvwxהסתyzטזץ",
+      35u,
+      visualX05,
+      visualY05,
+      logicalIndex05
+    },
+    {
+      "Multiline. Single bidirectional paragraph, starts RTL, wrapped lines",
+      "שנבabcגקכdefעיןghiחלךjklצמםmnoפרףpqrדאוstuהסתv"
+      "wxטזץyzשנבabcגקכdefעיןghiחלךjklצמםmnoפרףpqrדאוs"
+      "tuהסתvwxטזץyzשנבabcגקכdefעיןghiחלךjklצמםmnoפרף"
+      "pqrדאוstuהסתvwxטזץyzשנבabcגקכdefעיןghiחלךjklצמם"
+      "mnoפרףpqrדאוstuהסתvwxטזץyzשנבabcגקכdefעיןghiחל"
+      "ךjklצמםmnoפרףpqrדאוstuהסתvwxטזץyz",
+      35u,
+      visualX06,
+      visualY06,
+      logicalIndex06
+    }
+  };
+  const unsigned int numberOfTests = 6u;
+
+  for( unsigned int index = 0u; index < numberOfTests; ++index )
+  {
+    ToolkitTestApplication application;
+    if( !GetClosestCursorIndexTest( data[index] ) )
+    {
+      tet_result(TET_FAIL);
+    }
+  }
+
+  tet_result(TET_PASS);
+  END_TEST;
+}
index 521cbf9..219fba1 100644 (file)
@@ -86,8 +86,9 @@ bool LayoutTextTest( const LayoutTextData& data )
   fontClient.GetFontId( pathName + DEFAULT_FONT_DIR + "/tizen/TizenSansArabicRegular.ttf" );
 
   // 1) Create the model.
-  LogicalModelPtr logicalModel = LogicalModel::New();
-  VisualModelPtr visualModel = VisualModel::New();
+  LogicalModelPtr logicalModel;
+  VisualModelPtr visualModel;
+  MetricsPtr metrics;
   Size layoutSize;
 
   Vector<FontDescriptionRun> fontDescriptionRuns;
@@ -107,7 +108,8 @@ bool LayoutTextTest( const LayoutTextData& data )
                    options,
                    layoutSize,
                    logicalModel,
-                   visualModel );
+                   visualModel,
+                   metrics );
 
   // 2) Clear the layout.
   Vector<LineRun>& lines = visualModel->mLines;
@@ -150,8 +152,6 @@ bool LayoutTextTest( const LayoutTextData& data )
                         glyphPositions.Begin() + data.startIndex + data.numberOfGlyphs );
 
   // 3) Layout
-  MetricsPtr metrics = Metrics::New( fontClient );
-
   LayoutEngine engine;
   engine.SetMetrics( metrics );
   engine.SetTextEllipsisEnabled( data.ellipsis );
@@ -343,8 +343,9 @@ bool ReLayoutRightToLeftLinesTest( const ReLayoutRightToLeftLinesData& data )
   fontClient.GetFontId( pathName + DEFAULT_FONT_DIR + "/tizen/TizenSansArabicRegular.ttf" );
 
   // 1) Create the model.
-  LogicalModelPtr logicalModel = LogicalModel::New();
-  VisualModelPtr visualModel = VisualModel::New();
+  LogicalModelPtr logicalModel;
+  VisualModelPtr visualModel;
+  MetricsPtr metrics;
   Size layoutSize;
 
   Vector<FontDescriptionRun> fontDescriptionRuns;
@@ -364,11 +365,10 @@ bool ReLayoutRightToLeftLinesTest( const ReLayoutRightToLeftLinesData& data )
                    options,
                    layoutSize,
                    logicalModel,
-                   visualModel );
+                   visualModel,
+                   metrics );
 
   // 2) Call the ReLayoutRightToLeftLines() method.
-  MetricsPtr metrics = Metrics::New( fontClient );
-
   LayoutEngine engine;
   engine.SetMetrics( metrics );
 
@@ -452,8 +452,9 @@ bool AlignTest( const AlignData& data )
   fontClient.GetFontId( pathName + DEFAULT_FONT_DIR + "/tizen/TizenSansArabicRegular.ttf" );
 
   // 1) Create the model.
-  LogicalModelPtr logicalModel = LogicalModel::New();
-  VisualModelPtr visualModel = VisualModel::New();
+  LogicalModelPtr logicalModel;
+  VisualModelPtr visualModel;
+  MetricsPtr metrics;
   Size layoutSize;
 
   Vector<FontDescriptionRun> fontDescriptionRuns;
@@ -472,11 +473,10 @@ bool AlignTest( const AlignData& data )
                    options,
                    layoutSize,
                    logicalModel,
-                   visualModel );
+                   visualModel,
+                   metrics );
 
   // Call the Align method.
-  MetricsPtr metrics = Metrics::New( fontClient );
-
   LayoutEngine engine;
   engine.SetMetrics( metrics );
 
index 9e830ec..7b319fb 100644 (file)
@@ -112,8 +112,9 @@ struct ShapeInfoData
 bool ShapeInfoTest( const ShapeInfoData& data )
 {
   // 1) Create the model.
-  LogicalModelPtr logicalModel = LogicalModel::New();
-  VisualModelPtr visualModel = VisualModel::New();
+  LogicalModelPtr logicalModel;
+  VisualModelPtr visualModel;
+  MetricsPtr metrics;
   Size textArea(100.f, 60.f);
   Size layoutSize;
 
@@ -125,7 +126,8 @@ bool ShapeInfoTest( const ShapeInfoData& data )
                    options,
                    layoutSize,
                    logicalModel,
-                   visualModel );
+                   visualModel,
+                   metrics );
 
   // 2) Clear the model.
 
@@ -267,7 +269,6 @@ bool ShapeInfoTest( const ShapeInfoData& data )
 
 int UtcDaliTextShape(void)
 {
-  ToolkitTestApplication application;
   tet_infoline(" UtcDaliTextShape");
 
   struct GlyphInfoData glyphs02[] =
@@ -521,6 +522,7 @@ int UtcDaliTextShape(void)
 
   for( unsigned int index = 0u; index < numberOfTests; ++index )
   {
+    ToolkitTestApplication application;
     if( !ShapeInfoTest( data[index] ) )
     {
       tet_result(TET_FAIL);
index 115a0d1..11b09f1 100644 (file)
@@ -65,8 +65,9 @@ struct SetCharacterToGlyphData
 bool SetGlyphsPerCharacterTest( const SetGlyphsPerCharacterData& data )
 {
   // 1) Create the model.
-  LogicalModelPtr logicalModel = LogicalModel::New();
-  VisualModelPtr visualModel = VisualModel::New();
+  LogicalModelPtr logicalModel;
+  VisualModelPtr visualModel;
+  MetricsPtr metrics;
   Size textArea(100.f, 60.f);
   Size layoutSize;
 
@@ -78,7 +79,8 @@ bool SetGlyphsPerCharacterTest( const SetGlyphsPerCharacterData& data )
                    options,
                    layoutSize,
                    logicalModel,
-                   visualModel );
+                   visualModel,
+                   metrics );
 
   Vector<GlyphIndex>& charactersToGlyph = visualModel->mCharactersToGlyph;
   Vector<Length>& glyphsPerCharacter = visualModel->mGlyphsPerCharacter;
@@ -145,8 +147,9 @@ bool SetGlyphsPerCharacterTest( const SetGlyphsPerCharacterData& data )
 bool SetCharacterToGlyphTest( const SetCharacterToGlyphData& data )
 {
   // 1) Create the model.
-  LogicalModelPtr logicalModel = LogicalModel::New();
-  VisualModelPtr visualModel = VisualModel::New();
+  LogicalModelPtr logicalModel;
+  VisualModelPtr visualModel;
+  MetricsPtr metrics;
   Size textArea(100.f, 60.f);
   Size layoutSize;
 
@@ -158,7 +161,8 @@ bool SetCharacterToGlyphTest( const SetCharacterToGlyphData& data )
                    options,
                    layoutSize,
                    logicalModel,
-                   visualModel );
+                   visualModel,
+                   metrics );
 
   Vector<GlyphIndex>& charactersToGlyph = visualModel->mCharactersToGlyph;
   Vector<Length>& glyphsPerCharacter = visualModel->mGlyphsPerCharacter;
@@ -226,7 +230,6 @@ bool SetCharacterToGlyphTest( const SetCharacterToGlyphData& data )
 
 int UtcDaliSetGlyphsPerCharacter(void)
 {
-  ToolkitTestApplication application;
   tet_infoline(" UtcDaliSetGlyphsPerCharacter");
 
   Length glyphsPerCharacter02[] = { 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u, 1u };
@@ -293,6 +296,7 @@ int UtcDaliSetGlyphsPerCharacter(void)
 
   for( unsigned int index = 0u; index < numberOfTests; ++index )
   {
+    ToolkitTestApplication application;
     if( !SetGlyphsPerCharacterTest( data[index] ) )
     {
       tet_result(TET_FAIL);
@@ -305,7 +309,6 @@ int UtcDaliSetGlyphsPerCharacter(void)
 
 int UtcDaliSetCharacterToGlyph(void)
 {
-  ToolkitTestApplication application;
   tet_infoline(" UtcDaliSetGlyphsPerCharacter");
 
   GlyphIndex glyphIndices02[] = { 0u, 1u, 2u, 3u, 4u, 5u, 6u, 7u, 8u, 9u, 10u };
@@ -370,6 +373,7 @@ int UtcDaliSetCharacterToGlyph(void)
 
   for( unsigned int index = 0u; index < numberOfTests; ++index )
   {
+    ToolkitTestApplication application;
     if( !SetCharacterToGlyphTest( data[index] ) )
     {
       tet_result(TET_FAIL);
index 030effc..2b5e328 100644 (file)
@@ -31,7 +31,9 @@ namespace
   Debug::Filter* gLogFilter = Debug::Filter::New(Debug::NoLogging, true, "LOG_TEXT_CONTROLS");
 #endif
 
-} // namespace
+const Dali::Toolkit::Text::CharacterDirection LTR = false; ///< Left To Right direction.
+
+} //namespace
 
 namespace Dali
 {
@@ -49,12 +51,17 @@ LineIndex GetClosestLine( VisualModelPtr visualModel,
   LineIndex lineIndex = 0u;
 
   const Vector<LineRun>& lines = visualModel->mLines;
-  for( LineIndex endLine = lines.Count();
-       lineIndex < endLine;
-       ++lineIndex )
+
+  for( Vector<LineRun>::ConstIterator it = lines.Begin(),
+         endIt = lines.End();
+       it != endIt;
+       ++it, ++lineIndex )
   {
-    const LineRun& lineRun = lines[lineIndex];
-    totalHeight += lineRun.ascender + -lineRun.descender;
+    const LineRun& lineRun = *it;
+
+    // The line height is the addition of the line ascender and the line descender.
+    // However, the line descender has a negative value, hence the subtraction.
+    totalHeight += lineRun.ascender - lineRun.descender;
 
     if( visualY < totalHeight )
     {
@@ -62,7 +69,7 @@ LineIndex GetClosestLine( VisualModelPtr visualModel,
     }
   }
 
-  if( lineIndex == 0 )
+  if( lineIndex == 0u )
   {
     return 0;
   }
@@ -70,6 +77,26 @@ LineIndex GetClosestLine( VisualModelPtr visualModel,
   return lineIndex-1;
 }
 
+float CalculateLineOffset( const Vector<LineRun>& lines,
+                           LineIndex lineIndex )
+{
+  float offset = 0.f;
+
+  for( Vector<LineRun>::ConstIterator it = lines.Begin(),
+         endIt = lines.Begin() + lineIndex;
+       it != endIt;
+       ++it )
+  {
+    const LineRun& lineRun = *it;
+
+    // The line height is the addition of the line ascender and the line descender.
+    // However, the line descender has a negative value, hence the subtraction.
+    offset += lineRun.ascender - lineRun.descender;
+  }
+
+  return offset;
+}
+
 CharacterIndex GetClosestCursorIndex( VisualModelPtr visualModel,
                                       LogicalModelPtr logicalModel,
                                       MetricsPtr metrics,
@@ -94,11 +121,12 @@ CharacterIndex GetClosestCursorIndex( VisualModelPtr visualModel,
 
   // Convert from text's coords to line's coords.
   const LineRun& line = *( visualModel->mLines.Begin() + lineIndex );
+
+  // Transform the tap point from text's coords to line's coords.
   visualX -= line.alignmentOffset;
 
   // Get the positions of the glyphs.
-  const Vector<Vector2>& positions = visualModel->mGlyphPositions;
-  const Vector2* const positionsBuffer = positions.Begin();
+  const Vector2* const positionsBuffer = visualModel->mGlyphPositions.Begin();
 
   // Get the character to glyph conversion table.
   const GlyphIndex* const charactersToGlyphBuffer = visualModel->mCharactersToGlyph.Begin();
@@ -106,6 +134,9 @@ CharacterIndex GetClosestCursorIndex( VisualModelPtr visualModel,
   // Get the glyphs per character table.
   const Length* const glyphsPerCharacterBuffer = visualModel->mGlyphsPerCharacter.Begin();
 
+  // Get the characters per glyph table.
+  const Length* const charactersPerGlyphBuffer = visualModel->mCharactersPerGlyph.Begin();
+
   // Get the glyph's info buffer.
   const GlyphInfo* const glyphInfoBuffer = visualModel->mGlyphs.Begin();
 
@@ -116,29 +147,29 @@ CharacterIndex GetClosestCursorIndex( VisualModelPtr visualModel,
   // Whether this line is a bidirectional line.
   const bool bidiLineFetched = logicalModel->FetchBidirectionalLineInfo( startCharacter );
 
+  // 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;
 
   // Traverses glyphs in visual order. To do that use the visual to logical conversion table.
   CharacterIndex visualIndex = startCharacter;
-  Length numberOfCharacters = 0u;
-  for( ; !matched && ( visualIndex < endCharacter ); ++visualIndex )
+  Length numberOfVisualCharacters = 0u;
+  for( ; visualIndex < endCharacter; ++visualIndex )
   {
     // The character in logical order.
     const CharacterIndex characterLogicalOrderIndex = ( bidiLineFetched ? logicalModel->GetLogicalCharacterIndex( visualIndex ) : visualIndex );
-
-    // Get the script of the character.
-    const Script script = logicalModel->GetScript( characterLogicalOrderIndex );
+    const CharacterDirection direction = ( bidiLineFetched ? *( directionsBuffer + characterLogicalOrderIndex ) : LTR );
 
     // The number of glyphs for that character
     const Length numberOfGlyphs = *( glyphsPerCharacterBuffer + characterLogicalOrderIndex );
-    ++numberOfCharacters;
-
+    ++numberOfVisualCharacters;
 
     if( 0u != numberOfGlyphs )
     {
       // Get the first character/glyph of the group of glyphs.
-      const CharacterIndex firstVisualCharacterIndex = 1u + visualIndex - numberOfCharacters;
+      const CharacterIndex firstVisualCharacterIndex = 1u + visualIndex - numberOfVisualCharacters;
       const CharacterIndex firstLogicalCharacterIndex = ( bidiLineFetched ? logicalModel->GetLogicalCharacterIndex( firstVisualCharacterIndex ) : firstVisualCharacterIndex );
       const GlyphIndex firstLogicalGlyphIndex = *( charactersToGlyphBuffer + firstLogicalCharacterIndex );
 
@@ -154,12 +185,25 @@ CharacterIndex GetClosestCursorIndex( VisualModelPtr visualModel,
       const Vector2& position = *( positionsBuffer + firstLogicalGlyphIndex );
 
       // Whether the glyph can be split, like Latin ligatures fi, ff or Arabic ﻻ.
+      const 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.
+        visualIndex += numberOfCharacters - 1u;
+      }
+
+      // Get the script of the character.
+      const Script script = logicalModel->GetScript( characterLogicalOrderIndex );
+
       const bool isInterglyphIndex = ( numberOfCharacters > numberOfGlyphs ) && HasLigatureMustBreak( script );
       const Length numberOfBlocks = isInterglyphIndex ? numberOfCharacters : 1u;
       const float glyphAdvance = glyphMetrics.advance / static_cast<float>( numberOfBlocks );
 
-      GlyphIndex index = 0u;
-      for( ; !matched && ( index < numberOfBlocks ); ++index )
+      CharacterIndex index = 0u;
+      for( ; index < numberOfBlocks; ++index )
       {
         // Find the mid-point of the area containing the glyph
         const float glyphCenter = -glyphMetrics.xBearing + position.x + ( static_cast<float>( index ) + 0.5f ) * glyphAdvance;
@@ -173,23 +217,83 @@ CharacterIndex GetClosestCursorIndex( VisualModelPtr visualModel,
 
       if( matched )
       {
+        // If the glyph is shaped from more than one character, it matches the character of the glyph.
         visualIndex = firstVisualCharacterIndex + index;
         break;
       }
 
-      numberOfCharacters = 0u;
+      numberOfVisualCharacters = 0u;
     }
-
   }
 
+  // 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 no character is matched, then the last character (in visual order) of the line is used.
     visualIndex = endCharacter;
   }
 
+  // Get the paragraph direction.
+  const CharacterDirection paragraphDirection = line.direction;
+
+  if( totalNumberOfCharacters != visualIndex )
+  {
+    // The visual index is not at the end of the text.
+
+    if( LTR == paragraphDirection )
+    {
+      // The paragraph direction is left to right.
+
+      if( visualIndex == endCharacter )
+      {
+        // It places the cursor just before the last character in visual order.
+        // i.e. it places the cursor just before the '\n' or before the last character
+        // if there is a long line with no word breaks which is wrapped.
+
+        // It doesn't check if the closest line is the last one like the RTL branch below
+        // because the total number of characters is different than the visual index and
+        // the visual index is the last character of the line.
+        --visualIndex;
+      }
+    }
+    else
+    {
+      // The paragraph direction is right to left.
+
+      if( ( lineIndex != numberOfLines - 1u ) && // is not the last line.
+          ( visualIndex == startCharacter ) )
+      {
+        // It places the cursor just after the first character in visual order.
+        // i.e. it places the cursor just after the '\n' or after the last character
+        // if there is a long line with no word breaks which is wrapped.
+
+        // If the last line doesn't end with '\n' it won't increase the visual index
+        // placing the cursor at the beginning of the line (in visual order).
+        ++visualIndex;
+      }
+    }
+  }
+  else
+  {
+    // The visual index is at the end of text.
+
+    // If the text ends with a new paragraph character i.e. a '\n', an extra line with no characters is added at the end of the text.
+    // 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 ) &&
+        TextAbstraction::IsNewParagraph( *( logicalModel->mText.Begin() + visualIndex - 1u ) ) )
+    {
+      --visualIndex;
+    }
+  }
+
   logicalIndex = ( bidiLineFetched ? logicalModel->GetLogicalCursorIndex( visualIndex ) : visualIndex );
+
   DALI_LOG_INFO( gLogFilter, Debug::Verbose, "closest visualIndex %d logicalIndex %d\n", visualIndex, logicalIndex );
 
   DALI_ASSERT_DEBUG( ( logicalIndex <= logicalModel->mText.Count() && logicalIndex >= 0 ) && "GetClosestCursorIndex - Out of bounds index" );
@@ -204,192 +308,261 @@ void GetCursorPosition( VisualModelPtr visualModel,
                         CharacterIndex logical,
                         CursorInfo& cursorInfo )
 {
-  // TODO: Check for multiline with \n, etc...
-
-  const Length numberOfCharacters = logicalModel->mText.Count();
-
-  // Check if the logical position is the first or the last one of the text.
-  const bool isFirstPosition = 0u == logical;
-  const bool isLastPosition = numberOfCharacters == logical;
+  // Whether the logical cursor position is at the end of the whole text.
+  const bool isLastPosition = logicalModel->mText.Count() == logical;
 
-  // 'logical' is the logical 'cursor' index.
-  // Get the next and current logical 'character' index.
-  const CharacterIndex nextCharacterIndex = logical;
-  const CharacterIndex characterIndex = isFirstPosition ? logical : logical - 1u;
-
-  // Get the direction of the character and the next one.
-  const CharacterDirection* const modelCharacterDirectionsBuffer = ( 0u != logicalModel->mCharacterDirections.Count() ) ? logicalModel->mCharacterDirections.Begin() : NULL;
+  // Get the line where the character is laid-out.
+  const CharacterIndex characterOfLine = isLastPosition ? ( logical - 1u ) : logical;
 
-  CharacterDirection isCurrentRightToLeft = false;
-  CharacterDirection isNextRightToLeft = false;
-  if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
-  {
-    isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + characterIndex );
-    isNextRightToLeft = *( modelCharacterDirectionsBuffer + nextCharacterIndex );
-  }
+  // 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 ) );
 
-  // Get the line where the character is laid-out.
   const LineRun* const modelLines = visualModel->mLines.Begin();
 
-  const LineIndex lineIndex = visualModel->GetLineOfCharacter( characterIndex );
+  const LineIndex lineIndex = visualModel->GetLineOfCharacter( characterOfLine );
   const LineRun& line = *( modelLines + lineIndex );
 
-  // Get the paragraph's direction.
-  const CharacterDirection isRightToLeftParagraph = line.direction;
+  if( isLastNewParagraph )
+  {
+    // The cursor is in a new line with no characters. Place the cursor in that line.
+    const LineIndex newLineIndex = lineIndex + 1u;
+    const LineRun& newLine = *( modelLines + newLineIndex );
+
+    cursorInfo.isSecondaryCursor = false;
 
-  // Check whether there is an alternative position:
+    // Set the line offset and height.
+    cursorInfo.lineOffset = CalculateLineOffset( visualModel->mLines,
+                                                 newLineIndex );
 
-  cursorInfo.isSecondaryCursor = ( !isLastPosition && ( isCurrentRightToLeft != isNextRightToLeft ) ) ||
-                                 ( isLastPosition && ( isRightToLeftParagraph != isCurrentRightToLeft ) );
+    // The line height is the addition of the line ascender and the line descender.
+    // However, the line descender has a negative value, hence the subtraction.
+    cursorInfo.lineHeight = newLine.ascender - newLine.descender;
 
-  // Set the line offset and height.
-  cursorInfo.lineOffset = 0.f;
-  cursorInfo.lineHeight = line.ascender + -line.descender;
+    // Set the primary cursor's height.
+    cursorInfo.primaryCursorHeight = cursorInfo.lineHeight;
 
-  // Calculate the primary cursor.
+    // Set the primary cursor's position.
+    cursorInfo.primaryPosition.x = 0.f;
+    cursorInfo.primaryPosition.y = cursorInfo.lineOffset;
 
-  CharacterIndex index = characterIndex;
-  if( cursorInfo.isSecondaryCursor )
+    // Transform the cursor info from line's coords to text's coords.
+    cursorInfo.primaryPosition.x += ( LTR == line.direction ) ? 0.f : visualModel->mControlSize.width;
+  }
+  else
   {
-    // If there is a secondary position, the primary cursor may be in a different place than the logical index.
+    // Whether this line is a bidirectional line.
+    const bool bidiLineFetched = logicalModel->FetchBidirectionalLineInfo( characterOfLine );
 
-    if( isLastPosition )
-    {
-      // The position of the cursor after the last character needs special
-      // care depending on its direction and the direction of the paragraph.
+    // 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;
 
-      // Need to find the first character after the last character with the paragraph's direction.
-      // i.e l0 l1 l2 r0 r1 should find r0.
+    // '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;
 
-      // TODO: check for more than one line!
-      index = isRightToLeftParagraph ? line.characterRun.characterIndex : line.characterRun.characterIndex + line.characterRun.numberOfCharacters - 1u;
-      index = logicalModel->GetLogicalCharacterIndex( index );
-    }
-    else
+    // The character's direction buffer.
+    const CharacterDirection* const directionsBuffer = bidiLineFetched ? logicalModel->mCharacterDirections.Begin() : NULL;
+
+    CharacterDirection isCurrentRightToLeft = false;
+    CharacterDirection isNextRightToLeft = false;
+    if( bidiLineFetched ) // If bidiLineFetched is false, it means the whole text is left to right.
     {
-      index = ( isRightToLeftParagraph == isCurrentRightToLeft ) ? characterIndex : nextCharacterIndex;
+      isCurrentRightToLeft = *( directionsBuffer + characterIndex );
+      isNextRightToLeft = *( directionsBuffer + nextCharacterIndex );
     }
-  }
 
-  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();
+    // Get the paragraph's direction.
+    const CharacterDirection isRightToLeftParagraph = line.direction;
 
-  // Convert the cursor position into the glyph position.
-  const GlyphIndex primaryGlyphIndex = *( charactersToGlyphBuffer + index );
-  const Length primaryNumberOfGlyphs = *( glyphsPerCharacterBuffer + index );
-  const Length primaryNumberOfCharacters = *( charactersPerGlyphBuffer + primaryGlyphIndex );
-
-  // Get the metrics for the group of glyphs.
-  GlyphMetrics glyphMetrics;
-  GetGlyphsMetrics( primaryGlyphIndex,
-                    primaryNumberOfGlyphs,
-                    glyphMetrics,
-                    glyphInfoBuffer,
-                    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,
-  //     if the logical cursor is one, the position is the position of the first glyph and the advance is added.
-  // A 'truth table' was build and an online Karnaugh map tool was used to simplify the logic.
-  //
-  // FLCP A
-  // ------
-  // 0000 1
-  // 0001 1
-  // 0010 0
-  // 0011 0
-  // 0100 1
-  // 0101 0
-  // 0110 1
-  // 0111 0
-  // 1000 0
-  // 1001 x
-  // 1010 x
-  // 1011 1
-  // 1100 x
-  // 1101 x
-  // 1110 x
-  // 1111 x
-  //
-  // Where F -> isFirstPosition
-  //       L -> isLastPosition
-  //       C -> isCurrentRightToLeft
-  //       P -> isRightToLeftParagraph
-  //       A -> Whether to add the glyph's advance.
-
-  const bool addGlyphAdvance = ( ( isLastPosition && !isRightToLeftParagraph ) ||
-                                 ( isFirstPosition && isRightToLeftParagraph ) ||
-                                 ( !isFirstPosition && !isLastPosition && !isCurrentRightToLeft ) );
-
-  float glyphAdvance = addGlyphAdvance ? glyphMetrics.advance : 0.f;
-
-  if( !isLastPosition &&
-      ( primaryNumberOfCharacters > 1u ) )
-  {
-    const CharacterIndex firstIndex = *( glyphsToCharactersBuffer + primaryGlyphIndex );
+    // Check whether there is an alternative position:
+    cursorInfo.isSecondaryCursor = ( ( !isLastPositionOfLine && ( isCurrentRightToLeft != isNextRightToLeft ) )     ||
+                                     ( isLastPositionOfLine && ( isRightToLeftParagraph != isCurrentRightToLeft ) ) ||
+                                     ( isFirstPositionOfLine && ( isRightToLeftParagraph != isCurrentRightToLeft ) ) );
+
+    // Set the line offset and height.
+    cursorInfo.lineOffset = CalculateLineOffset( visualModel->mLines,
+                                                 lineIndex );
 
-    bool isCurrentRightToLeft = false;
-    if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
+    // The line height is the addition of the line ascender and the line descender.
+    // However, the line descender has a negative value, hence the subtraction.
+    cursorInfo.lineHeight = line.ascender - line.descender;
+
+    // Calculate the primary cursor.
+
+    CharacterIndex index = characterIndex;
+    if( cursorInfo.isSecondaryCursor )
     {
-      isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + index );
+      // If there is a secondary position, the primary cursor may be in a different place than the logical index.
+
+      if( isLastPositionOfLine )
+      {
+        // The position of the cursor after the last character needs special
+        // care depending on its direction and the direction of the paragraph.
+
+        // Need to find the first character after the last character with the paragraph's direction.
+        // i.e l0 l1 l2 r0 r1 should find r0.
+
+        index = isRightToLeftParagraph ? line.characterRun.characterIndex : line.characterRun.characterIndex + line.characterRun.numberOfCharacters - 1u;
+        if( bidiLineFetched )
+        {
+          index = logicalModel->GetLogicalCharacterIndex( index );
+        }
+      }
+      else if( isFirstPositionOfLine )
+      {
+        index = isRightToLeftParagraph ? line.characterRun.characterIndex + line.characterRun.numberOfCharacters - 1u : line.characterRun.characterIndex;
+        if( bidiLineFetched )
+        {
+          index = logicalModel->GetLogicalCharacterIndex( index );
+        }
+      }
+      else
+      {
+        index = ( isRightToLeftParagraph == isCurrentRightToLeft ) ? characterIndex : nextCharacterIndex;
+      }
     }
 
-    Length numberOfGlyphAdvance = ( isFirstPosition ? 0u : 1u ) + characterIndex - firstIndex;
-    if( isCurrentRightToLeft )
+    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();
+
+    // Convert the cursor position into the glyph position.
+    const GlyphIndex primaryGlyphIndex = *( charactersToGlyphBuffer + index );
+    const Length primaryNumberOfGlyphs = *( glyphsPerCharacterBuffer + index );
+    const Length primaryNumberOfCharacters = *( charactersPerGlyphBuffer + primaryGlyphIndex );
+
+    // Get the metrics for the group of glyphs.
+    GlyphMetrics glyphMetrics;
+    GetGlyphsMetrics( primaryGlyphIndex,
+                      primaryNumberOfGlyphs,
+                      glyphMetrics,
+                      glyphInfoBuffer,
+                      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,
+    //     if the logical cursor is one, the position is the position of the first glyph and the advance is added.
+    // A 'truth table' was build and an online Karnaugh map tool was used to simplify the logic.
+    //
+    // FLCP A
+    // ------
+    // 0000 1
+    // 0001 1
+    // 0010 0
+    // 0011 0
+    // 0100 1
+    // 0101 0
+    // 0110 1
+    // 0111 0
+    // 1000 0
+    // 1001 1
+    // 1010 0
+    // 1011 1
+    // 1100 x
+    // 1101 x
+    // 1110 x
+    // 1111 x
+    //
+    // Where F -> isFirstPosition
+    //       L -> isLastPosition
+    //       C -> isCurrentRightToLeft
+    //       P -> isRightToLeftParagraph
+    //       A -> Whether to add the glyph's advance.
+
+    const bool addGlyphAdvance = ( ( isLastPositionOfLine && !isRightToLeftParagraph ) ||
+                                   ( isFirstPositionOfLine && isRightToLeftParagraph ) ||
+                                   ( !isFirstPositionOfLine && !isLastPosition && !isCurrentRightToLeft ) );
+
+    float glyphAdvance = addGlyphAdvance ? glyphMetrics.advance : 0.f;
+
+    if( !isLastPositionOfLine &&
+        ( primaryNumberOfCharacters > 1u ) )
     {
-      numberOfGlyphAdvance = primaryNumberOfCharacters - numberOfGlyphAdvance;
-    }
+      const CharacterIndex firstIndex = *( glyphsToCharactersBuffer + primaryGlyphIndex );
 
-    glyphAdvance = static_cast<float>( numberOfGlyphAdvance ) * glyphMetrics.advance / static_cast<float>( primaryNumberOfCharacters );
-  }
+      bool isCurrentRightToLeft = false;
+      if( bidiLineFetched ) // If bidiLineFetched is false, it means the whole text is left to right.
+      {
+        isCurrentRightToLeft = *( directionsBuffer + index );
+      }
 
-  // Get the glyph position and x bearing (in the line's coords).
-  const Vector2& primaryPosition = *( glyphPositionsBuffer + primaryGlyphIndex );
+      Length numberOfGlyphAdvance = ( isFirstPositionOfLine ? 0u : 1u ) + characterIndex - firstIndex;
+      if( isCurrentRightToLeft )
+      {
+        numberOfGlyphAdvance = primaryNumberOfCharacters - numberOfGlyphAdvance;
+      }
 
-  // Set the primary cursor's height.
-  cursorInfo.primaryCursorHeight = cursorInfo.isSecondaryCursor ? 0.5f * glyphMetrics.fontHeight : glyphMetrics.fontHeight;
+      glyphAdvance = static_cast<float>( numberOfGlyphAdvance ) * glyphMetrics.advance / static_cast<float>( primaryNumberOfCharacters );
+    }
 
-  // Set the primary cursor's position.
-  cursorInfo.primaryPosition.x = -glyphMetrics.xBearing + primaryPosition.x + glyphAdvance;
-  cursorInfo.primaryPosition.y = line.ascender - glyphMetrics.ascender;
+    // Get the glyph position and x bearing (in the line's coords).
+    const Vector2& primaryPosition = *( glyphPositionsBuffer + primaryGlyphIndex );
 
-  // Transform the cursor info from line's coords to text's coords.
-  cursorInfo.primaryPosition.x += line.alignmentOffset;
+    // Set the primary cursor's height.
+    cursorInfo.primaryCursorHeight = cursorInfo.isSecondaryCursor ? 0.5f * glyphMetrics.fontHeight : glyphMetrics.fontHeight;
 
-  // Calculate the secondary cursor.
+    // Set the primary cursor's position.
+    cursorInfo.primaryPosition.x = -glyphMetrics.xBearing + primaryPosition.x + glyphAdvance;
+    cursorInfo.primaryPosition.y = cursorInfo.lineOffset + line.ascender - glyphMetrics.ascender;
 
-  if( cursorInfo.isSecondaryCursor )
-  {
-    // Set the secondary cursor's height.
-    cursorInfo.secondaryCursorHeight = 0.5f * glyphMetrics.fontHeight;
+    // Transform the cursor info from line's coords to text's coords.
+    cursorInfo.primaryPosition.x += line.alignmentOffset;
 
-    CharacterIndex index = characterIndex;
-    if( !isLastPosition )
+    // Calculate the secondary cursor.
+    if( cursorInfo.isSecondaryCursor )
     {
-      index = ( isRightToLeftParagraph == isCurrentRightToLeft ) ? nextCharacterIndex : characterIndex;
-    }
+      // Set the secondary cursor's height.
+      cursorInfo.secondaryCursorHeight = 0.5f * glyphMetrics.fontHeight;
 
-    const GlyphIndex secondaryGlyphIndex = *( charactersToGlyphBuffer + index );
-    const Length secondaryNumberOfGlyphs = *( glyphsPerCharacterBuffer + index );
+      CharacterIndex index = characterIndex;
+      if( !isLastPositionOfLine )
+      {
+        index = ( isRightToLeftParagraph == isCurrentRightToLeft ) ? nextCharacterIndex : characterIndex;
+      }
 
-    const Vector2& secondaryPosition = *( glyphPositionsBuffer + secondaryGlyphIndex );
+      const GlyphIndex secondaryGlyphIndex = *( charactersToGlyphBuffer + index );
+      const Length secondaryNumberOfGlyphs = *( glyphsPerCharacterBuffer + index );
 
-    GetGlyphsMetrics( secondaryGlyphIndex,
-                      secondaryNumberOfGlyphs,
-                      glyphMetrics,
-                      glyphInfoBuffer,
-                      metrics );
+      const Vector2& secondaryPosition = *( glyphPositionsBuffer + secondaryGlyphIndex );
 
-    // Set the secondary cursor's position.
-    cursorInfo.secondaryPosition.x = -glyphMetrics.xBearing + secondaryPosition.x + ( isCurrentRightToLeft ? 0.f : glyphMetrics.advance );
-    cursorInfo.secondaryPosition.y = cursorInfo.lineHeight - cursorInfo.secondaryCursorHeight - line.descender - ( glyphMetrics.fontHeight - glyphMetrics.ascender );
+      GetGlyphsMetrics( secondaryGlyphIndex,
+                        secondaryNumberOfGlyphs,
+                        glyphMetrics,
+                        glyphInfoBuffer,
+                        metrics );
 
-    // Transform the cursor info from line's coords to text's coords.
-    cursorInfo.secondaryPosition.x += line.alignmentOffset;
+      // Set the secondary cursor's position.
+
+      // FCP A
+      // ------
+      // 000 1
+      // 001 x
+      // 010 0
+      // 011 0
+      // 100 x
+      // 101 0
+      // 110 1
+      // 111 x
+      //
+      // Where F -> isFirstPosition
+      //       C -> isCurrentRightToLeft
+      //       P -> isRightToLeftParagraph
+      //       A -> Whether to add the glyph's advance.
+
+      const bool addGlyphAdvance = ( ( !isFirstPositionOfLine && !isCurrentRightToLeft ) ||
+                                     ( isFirstPositionOfLine && !isRightToLeftParagraph ) );
+
+      cursorInfo.secondaryPosition.x = -glyphMetrics.xBearing + secondaryPosition.x + ( addGlyphAdvance ? glyphMetrics.advance : 0.f );
+      cursorInfo.secondaryPosition.y = cursorInfo.lineOffset + cursorInfo.lineHeight - cursorInfo.secondaryCursorHeight - line.descender - ( glyphMetrics.fontHeight - glyphMetrics.ascender );
+
+      // Transform the cursor info from line's coords to text's coords.
+      cursorInfo.secondaryPosition.x += line.alignmentOffset;
+    }
   }
 }
 
index 08c21a3..e89c335 100644 (file)
@@ -70,6 +70,19 @@ LineIndex GetClosestLine( VisualModelPtr visualModel,
                           float visualY );
 
 /**
+ * @brief Calculates the vertical line's offset for a given line.
+ *
+ * @pre @p lineIndex must be between 0 and the number of lines (both inclusive).
+ *
+ * @param[in] lines The laid-out lines.
+ * @param[in] lineIndex Index to the line.
+ *
+ * @return The vertical offset of the given line.
+ */
+float CalculateLineOffset( const Vector<LineRun>& lines,
+                           LineIndex lineIndex );
+
+/**
  * @brief Retrieves the cursor's logical position for a given touch point x,y
  *
  * @param[in] visualModel The visual model.
index ac9110e..bc2a372 100644 (file)
@@ -25,6 +25,7 @@
 
 // INTERNAL INCLUDES
 #include <dali-toolkit/internal/text/bidirectional-line-info-run.h>
+#include <dali-toolkit/internal/text/cursor-helper-functions.h>
 #include <dali-toolkit/internal/text/glyph-metrics-helper.h>
 #include <dali-toolkit/internal/text/layouts/layout-parameters.h>
 
@@ -464,34 +465,6 @@ struct LayoutEngine::Impl
     DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--GetLineLayoutForBox\n" );
   }
 
-  /**
-   * @brief Calculates the vertical offset to add to the new laid-out glyphs.
-   *
-   * @pre @p lineIndex must be between 0 and the number of lines (both inclusive).
-   *
-   * @param[in] lines The previously laid-out lines.
-   * @param[in] lineIndex Index to the line where the new laid-out lines are inserted.
-   *
-   * @return The vertical offset of the lines starting from the beginning to the line @p lineIndex.
-   */
-  float SetParagraphOffset( const Vector<LineRun>& lines,
-                            LineIndex lineIndex )
-  {
-    float offset = 0.f;
-
-    for( Vector<LineRun>::ConstIterator it = lines.Begin(),
-           endIt = lines.Begin() + lineIndex;
-         it != endIt;
-         ++it )
-    {
-      const LineRun& line = *it;
-
-      offset += line.ascender + -line.descender;
-    }
-
-    return offset;
-  }
-
   void SetGlyphPositions( const GlyphInfo* const glyphsBuffer,
                           Length numberOfGlyphs,
                           Vector2* glyphPositionsBuffer )
@@ -871,8 +844,8 @@ struct LayoutEngine::Impl
       linesBuffer = lines.Begin();
     }
 
-    float penY = SetParagraphOffset( lines,
-                                     layoutParameters.startLineIndex );
+    float penY = CalculateLineOffset( lines,
+                                      layoutParameters.startLineIndex );
 
     for( GlyphIndex index = layoutParameters.startGlyphIndex; index < lastGlyphPlusOne; )
     {