From 1fcc4ed0e91129dff19b946257bcee49560d9748 Mon Sep 17 00:00:00 2001 From: Victor Cebollada Date: Wed, 4 May 2016 14:38:12 +0100 Subject: [PATCH] Multi-line text. Cursor hit and cursor's position. Change-Id: I28f664ea7d1dfed61fcd4e69a3de8ec850b4df58 Signed-off-by: Victor Cebollada --- .../src/dali-toolkit-internal/CMakeLists.txt | 1 + .../dali-toolkit-test-utils/toolkit-text-model.cpp | 11 +- .../dali-toolkit-test-utils/toolkit-text-model.h | 7 +- .../utc-Dali-BidirectionalSupport.cpp | 40 +- .../utc-Dali-LogicalModel.cpp | 40 +- .../dali-toolkit-internal/utc-Dali-Text-Cursor.cpp | 381 +++++++++++++++ .../dali-toolkit-internal/utc-Dali-Text-Layout.cpp | 30 +- .../utc-Dali-Text-Shaping.cpp | 10 +- .../dali-toolkit-internal/utc-Dali-VisualModel.cpp | 20 +- .../internal/text/cursor-helper-functions.cpp | 509 ++++++++++++++------- .../internal/text/cursor-helper-functions.h | 13 + .../internal/text/layouts/layout-engine.cpp | 33 +- 12 files changed, 833 insertions(+), 262 deletions(-) create mode 100644 automated-tests/src/dali-toolkit-internal/utc-Dali-Text-Cursor.cpp diff --git a/automated-tests/src/dali-toolkit-internal/CMakeLists.txt b/automated-tests/src/dali-toolkit-internal/CMakeLists.txt index b2a4e29..b23014b 100644 --- a/automated-tests/src/dali-toolkit-internal/CMakeLists.txt +++ b/automated-tests/src/dali-toolkit-internal/CMakeLists.txt @@ -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 diff --git a/automated-tests/src/dali-toolkit-internal/dali-toolkit-test-utils/toolkit-text-model.cpp b/automated-tests/src/dali-toolkit-internal/dali-toolkit-test-utils/toolkit-text-model.cpp index 83fa45c..21d9f42 100644 --- a/automated-tests/src/dali-toolkit-internal/dali-toolkit-test-utils/toolkit-text-model.cpp +++ b/automated-tests/src/dali-toolkit-internal/dali-toolkit-test-utils/toolkit-text-model.cpp @@ -25,7 +25,6 @@ #include #include #include -#include #include #include #include @@ -97,9 +96,13 @@ void CreateTextModel( const std::string& text, const Vector& 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& 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 ); diff --git a/automated-tests/src/dali-toolkit-internal/dali-toolkit-test-utils/toolkit-text-model.h b/automated-tests/src/dali-toolkit-internal/dali-toolkit-test-utils/toolkit-text-model.h index 631b151..288627f 100644 --- a/automated-tests/src/dali-toolkit-internal/dali-toolkit-test-utils/toolkit-text-model.h +++ b/automated-tests/src/dali-toolkit-internal/dali-toolkit-test-utils/toolkit-text-model.h @@ -22,6 +22,7 @@ // INTERNAL INCLUDES #include +#include #include 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& fontDescriptions, const LayoutOptions& options, Size& layoutSize, - LogicalModelPtr logicalModel, - VisualModelPtr visualModel ); + LogicalModelPtr& logicalModel, + VisualModelPtr& visualModel, + MetricsPtr& metrics ); } // namespace Text diff --git a/automated-tests/src/dali-toolkit-internal/utc-Dali-BidirectionalSupport.cpp b/automated-tests/src/dali-toolkit-internal/utc-Dali-BidirectionalSupport.cpp index f61e539..629e4f3 100644 --- a/automated-tests/src/dali-toolkit-internal/utc-Dali-BidirectionalSupport.cpp +++ b/automated-tests/src/dali-toolkit-internal/utc-Dali-BidirectionalSupport.cpp @@ -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& bidirectionalInfo = logicalModel->mBidirectionalParagraphInfo; @@ -215,8 +217,9 @@ void FreeBidirectionalLineInfoResources( Vector 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 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& 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); diff --git a/automated-tests/src/dali-toolkit-internal/utc-Dali-LogicalModel.cpp b/automated-tests/src/dali-toolkit-internal/utc-Dali-LogicalModel.cpp index 9fe39c0..25a724c 100644 --- a/automated-tests/src/dali-toolkit-internal/utc-Dali-LogicalModel.cpp +++ b/automated-tests/src/dali-toolkit-internal/utc-Dali-LogicalModel.cpp @@ -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& 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 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 index 0000000..195c81b --- /dev/null +++ b/automated-tests/src/dali-toolkit-internal/utc-Dali-Text-Cursor.cpp @@ -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 + +#include + +#include +#include +#include +#include + + +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 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 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; +} diff --git a/automated-tests/src/dali-toolkit-internal/utc-Dali-Text-Layout.cpp b/automated-tests/src/dali-toolkit-internal/utc-Dali-Text-Layout.cpp index 521cbf9..219fba1 100644 --- a/automated-tests/src/dali-toolkit-internal/utc-Dali-Text-Layout.cpp +++ b/automated-tests/src/dali-toolkit-internal/utc-Dali-Text-Layout.cpp @@ -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 fontDescriptionRuns; @@ -107,7 +108,8 @@ bool LayoutTextTest( const LayoutTextData& data ) options, layoutSize, logicalModel, - visualModel ); + visualModel, + metrics ); // 2) Clear the layout. Vector& 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 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 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 ); diff --git a/automated-tests/src/dali-toolkit-internal/utc-Dali-Text-Shaping.cpp b/automated-tests/src/dali-toolkit-internal/utc-Dali-Text-Shaping.cpp index 9e830ec..7b319fb 100644 --- a/automated-tests/src/dali-toolkit-internal/utc-Dali-Text-Shaping.cpp +++ b/automated-tests/src/dali-toolkit-internal/utc-Dali-Text-Shaping.cpp @@ -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); diff --git a/automated-tests/src/dali-toolkit-internal/utc-Dali-VisualModel.cpp b/automated-tests/src/dali-toolkit-internal/utc-Dali-VisualModel.cpp index 115a0d1..11b09f1 100644 --- a/automated-tests/src/dali-toolkit-internal/utc-Dali-VisualModel.cpp +++ b/automated-tests/src/dali-toolkit-internal/utc-Dali-VisualModel.cpp @@ -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& charactersToGlyph = visualModel->mCharactersToGlyph; Vector& 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& charactersToGlyph = visualModel->mCharactersToGlyph; Vector& 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); diff --git a/dali-toolkit/internal/text/cursor-helper-functions.cpp b/dali-toolkit/internal/text/cursor-helper-functions.cpp index 030effc..2b5e328 100644 --- a/dali-toolkit/internal/text/cursor-helper-functions.cpp +++ b/dali-toolkit/internal/text/cursor-helper-functions.cpp @@ -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& lines = visualModel->mLines; - for( LineIndex endLine = lines.Count(); - lineIndex < endLine; - ++lineIndex ) + + for( Vector::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& lines, + LineIndex lineIndex ) +{ + float offset = 0.f; + + for( Vector::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& 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( 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( 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( numberOfGlyphAdvance ) * glyphMetrics.advance / static_cast( 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( numberOfGlyphAdvance ) * glyphMetrics.advance / static_cast( 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; + } } } diff --git a/dali-toolkit/internal/text/cursor-helper-functions.h b/dali-toolkit/internal/text/cursor-helper-functions.h index 08c21a3..e89c335 100644 --- a/dali-toolkit/internal/text/cursor-helper-functions.h +++ b/dali-toolkit/internal/text/cursor-helper-functions.h @@ -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& lines, + LineIndex lineIndex ); + +/** * @brief Retrieves the cursor's logical position for a given touch point x,y * * @param[in] visualModel The visual model. diff --git a/dali-toolkit/internal/text/layouts/layout-engine.cpp b/dali-toolkit/internal/text/layouts/layout-engine.cpp index ac9110e..bc2a372 100644 --- a/dali-toolkit/internal/text/layouts/layout-engine.cpp +++ b/dali-toolkit/internal/text/layouts/layout-engine.cpp @@ -25,6 +25,7 @@ // INTERNAL INCLUDES #include +#include #include #include @@ -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& lines, - LineIndex lineIndex ) - { - float offset = 0.f; - - for( Vector::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; ) { -- 2.7.4