From 5d397f414305c86782668ecd154d8967ee8396b0 Mon Sep 17 00:00:00 2001 From: ssabah Date: Wed, 16 Mar 2022 17:32:21 +0300 Subject: [PATCH] Handle Emoji clustering for cursor handling Checked cases: - cursor movement (arrows): it works fine - line-breaking: it works fine Resolved cases: - deletion: this patch handled delete cases when the cursor is before or after Emoji :: Before: when use delete key and cursor is before Emoji :: After: when use backspace key and cursor is after Emoji - Emoji layoutted at the end of line: this patch handle the below scenario 1) When there is Emoji contains multi unicodes and it is layoutted at the end of line (LineWrap case , is not new line case) 2) Try to click at the center or at the end of Emoji then the cursor appears inside Emoji 3) Example:"FamilyManWomanGirlBoy 👨‍👩‍👧‍👦" Sample code to test: ============================================================================ TextEditor textEditor = TextEditor::New(); textEditor.SetBackgroundColor(Dali::Color::GRAY); textEditor.SetProperty(Dali::Toolkit::TextEditor::Property::TEXT, "AB👨‍👩‍👧‍👦AB👩🏻‍🔬B👨‍👩‍👧‍👦AA☪︎B☪️AB"); textEditor.SetProperty(Actor::Property::ANCHOR_POINT, AnchorPoint::TOP_LEFT); textEditor.SetProperty(Dali::Actor::Property::POSITION, Vector2(10, 30)); textEditor.SetProperty(Dali::Actor::Property::SIZE, Vector2(100, 100)); textEditor.SetProperty(Dali::Toolkit::TextEditor::Property::ENABLE_MARKUP, true); textEditor.SetProperty(Dali::Toolkit::DevelTextEditor::Property::LINE_WRAP_MODE, Dali::Toolkit::DevelText::LineWrap::WORD); ============================================================================ Change-Id: I6d142c58c5cdef5e900404cca8b777a02c8488d1 --- .../src/dali-toolkit-internal/CMakeLists.txt | 1 + .../utc-Dali-Text-Characters.cpp | 189 +++++++++++++++++++++ .../dali-toolkit-internal/utc-Dali-Text-Cursor.cpp | 56 ++++-- .../src/dali-toolkit/utc-Dali-TextEditor.cpp | 79 +++++++++ .../src/dali-toolkit/utc-Dali-TextField.cpp | 79 +++++++++ dali-toolkit/internal/file.list | 1 + .../internal/text/characters-helper-functions.cpp | 60 +++++++ .../internal/text/characters-helper-functions.h | 52 ++++++ .../internal/text/cursor-helper-functions.cpp | 15 ++ .../internal/text/text-controller-text-updater.cpp | 22 +++ 10 files changed, 543 insertions(+), 11 deletions(-) create mode 100755 automated-tests/src/dali-toolkit-internal/utc-Dali-Text-Characters.cpp create mode 100644 dali-toolkit/internal/text/characters-helper-functions.cpp create mode 100644 dali-toolkit/internal/text/characters-helper-functions.h diff --git a/automated-tests/src/dali-toolkit-internal/CMakeLists.txt b/automated-tests/src/dali-toolkit-internal/CMakeLists.txt index 198d183..5ba86de 100755 --- a/automated-tests/src/dali-toolkit-internal/CMakeLists.txt +++ b/automated-tests/src/dali-toolkit-internal/CMakeLists.txt @@ -18,6 +18,7 @@ SET(TC_SOURCES utc-Dali-ItemView-internal.cpp utc-Dali-LogicalModel.cpp utc-Dali-PropertyHelper.cpp + utc-Dali-Text-Characters.cpp utc-Dali-Text-CharacterSetConversion.cpp utc-Dali-Text-Circular.cpp utc-Dali-Text-Controller.cpp diff --git a/automated-tests/src/dali-toolkit-internal/utc-Dali-Text-Characters.cpp b/automated-tests/src/dali-toolkit-internal/utc-Dali-Text-Characters.cpp new file mode 100755 index 0000000..259d682 --- /dev/null +++ b/automated-tests/src/dali-toolkit-internal/utc-Dali-Text-Characters.cpp @@ -0,0 +1,189 @@ +/* + * Copyright (c) 2022 Samsung Electronics Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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 +#include + +using namespace Dali; +using namespace Toolkit; +using namespace Text; + +// Tests the following function. +// +// CharacterRun RetrieveClusteredCharactersOfCharacterIndex(const VisualModelPtr& visualModel, +// const LogicalModelPtr& logicalModel, +// const CharacterIndex& characterIndex) + +////////////////////////////////////////////////////////// + +namespace +{ +const std::string DEFAULT_FONT_DIR("/resources/fonts"); +const unsigned int DEFAULT_FONT_SIZE = 1152u; + +struct RetrieveClusteredCharactersOfCharacterIndexData +{ + std::string description; ///< Description of the test. + std::string text; ///< Input text. + bool markupProcessorEnabled; //< Enable markup processor to use markup text. + unsigned int numberOfTests; ///< The number of tests. + CharacterIndex* characterIndex; ///< The character index for each test. + CharacterRun* clusteredCharacters; ///< The expected clustered characters run for each test. +}; + +bool GetRetrieveClusteredCharactersOfCharacterIndexTest(const RetrieveClusteredCharactersOfCharacterIndexData& data) +{ + std::cout << " testing : " << data.description << std::endl; + + // 1) Create the model. + ModelPtr textModel; + MetricsPtr metrics; + Size textArea(400.f, 600.f); + Size layoutSize; + + Vector fontDescriptionRuns; + LayoutOptions options; + CreateTextModel(data.text, + textArea, + fontDescriptionRuns, + options, + layoutSize, + textModel, + metrics, + data.markupProcessorEnabled, + LineWrap::WORD, + false, + Toolkit::DevelText::EllipsisPosition::END, + 0.0f, // lineSpacing + 0.0f // characterSpacing + ); + + LogicalModelPtr logicalModel = textModel->mLogicalModel; + VisualModelPtr visualModel = textModel->mVisualModel; + + for(unsigned int index = 0; index < data.numberOfTests; ++index) + { + CharacterRun clusteredCharacters = RetrieveClusteredCharactersOfCharacterIndex(visualModel, logicalModel, data.characterIndex[index]); + + if(clusteredCharacters.characterIndex != data.clusteredCharacters[index].characterIndex) + { + std::cout << " test " << index << " failed. Different clusteredCharacters.characterIndex : " << clusteredCharacters.characterIndex << ", expected : " << data.clusteredCharacters[index].characterIndex << std::endl; + return false; + } + + if(clusteredCharacters.numberOfCharacters != data.clusteredCharacters[index].numberOfCharacters) + { + std::cout << " test " << index << " failed. Different clusteredCharacters.numberOfCharacters : " << clusteredCharacters.numberOfCharacters << ", expected : " << data.clusteredCharacters[index].numberOfCharacters << std::endl; + return false; + } + } + + return true; +} + +} // namespace + +////////////////////////////////////////////////////////// +// +// UtcDaliRetrieveClusteredCharactersOfCharacterIndex +// +////////////////////////////////////////////////////////// + +int UtcDaliRetrieveClusteredCharactersOfCharacterIndex(void) +{ + tet_infoline(" UtcDaliRetrieveClusteredCharactersOfCharacterIndex"); + + CharacterIndex characterIndex01[] = {0u, 1u, 2u, 10u}; + + CharacterRun clusteredCharacters01[] = { + {0u, 1u}, + {1u, 1u}, + {2u, 1u}, + {10u, 1u}}; + + CharacterIndex characterIndex02[] = {0u, 4u, 6u}; + + CharacterRun clusteredCharacters02[] = { + {0u, 7u}, + {0u, 7u}, + {0u, 7u}}; + + CharacterIndex characterIndex03[] = {3u, 9u, 14u}; + + CharacterRun clusteredCharacters03[] = { + {2u, 7u}, + {9u, 1u}, + {11u, 4u}}; + + CharacterIndex characterIndex04[] = {0u, 1u, 2u, 10u}; + + CharacterRun clusteredCharacters04[] = { + {0u, 1u}, + {1u, 1u}, + {2u, 1u}, + {10u, 1u}}; + + struct RetrieveClusteredCharactersOfCharacterIndexData data[] = + { + {"Easy latin script", + "Hello world", + true, + 4u, + characterIndex01, + clusteredCharacters01}, + + {"FamilyManWomanGirlBoy Single Complex Emoji script", + "👨‍👩‍👧‍👦", + true, + 3u, + characterIndex02, + clusteredCharacters02}, + + {"Long text many Emojis with letters", + "AB👨‍👩‍👧‍👦AB👩🏻‍🔬B👨‍👩‍👧‍👦AA☪︎B☪️AB", + true, + 3u, + characterIndex03, + clusteredCharacters03}, + + {"Arabic script", + "اهلا و سهلا", + true, + 4u, + characterIndex04, + clusteredCharacters04}, + }; + const unsigned int numberOfTests = 4u; + + for(unsigned int index = 0; index < numberOfTests; ++index) + { + ToolkitTestApplication application; + if(!GetRetrieveClusteredCharactersOfCharacterIndexTest(data[index])) + { + tet_result(TET_FAIL); + } + } + + tet_result(TET_PASS); + END_TEST; +} 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 index 11c6343..cd1abcb 100755 --- a/automated-tests/src/dali-toolkit-internal/utc-Dali-Text-Cursor.cpp +++ b/automated-tests/src/dali-toolkit-internal/utc-Dali-Text-Cursor.cpp @@ -60,14 +60,15 @@ struct GetClosestLineData 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. - CharacterHitTest::Mode* mode; ///< The type of hit test. - CharacterIndex* logicalIndex; ///< The expected logical cursor index for each test. - bool* isCharacterHit; ///< The expected character hit value for each test. + 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. + CharacterHitTest::Mode* mode; ///< The type of hit test. + bool markupProcessorEnabled; //< Enable markup processor to use markup text. + CharacterIndex* logicalIndex; ///< The expected logical cursor index for each test. + bool* isCharacterHit; ///< The expected character hit value for each test. }; struct GetCursorPositionData @@ -173,7 +174,7 @@ bool GetClosestCursorIndexTest(const GetClosestCursorIndexData& data) layoutSize, textModel, metrics, - false, + data.markupProcessorEnabled, LineWrap::WORD, false, Toolkit::DevelText::EllipsisPosition::END, @@ -579,6 +580,12 @@ int UtcDaliGetClosestCursorIndex(void) CharacterIndex logicalIndex08[] = {1u}; bool isCharacterHit08[] = {true}; + float visualX09[] = {9.f}; + float visualY09[] = {12.f}; + CharacterHitTest::Mode mode09[] = {CharacterHitTest::TAP}; + CharacterIndex logicalIndex09[] = {1u}; + bool isCharacterHit09[] = {true}; + struct GetClosestCursorIndexData data[] = { {"Void text.", @@ -587,24 +594,30 @@ int UtcDaliGetClosestCursorIndex(void) visualX01, visualY01, mode01, + false, logicalIndex01, isCharacterHit01}, + {"Single line text.", "Hello world שלום עולם", 7u, visualX02, visualY02, mode02, + false, logicalIndex02, isCharacterHit02}, + {"Single line with ligatures", "different الأربعاء", 4u, visualX03, visualY03, mode03, + false, logicalIndex03, isCharacterHit03}, + {"Multiline. Single line paragraphs", "Hello world\n" "שלום עולם\n" @@ -613,8 +626,10 @@ int UtcDaliGetClosestCursorIndex(void) visualX04, visualY04, mode04, + false, logicalIndex04, isCharacterHit04}, + {"Multiline. Single bidirectional paragraph, starts LTR, wrapped lines", "abcשנבdefגקכghiעיןjklחלךmnoצמםpqrפרףstuדאוvwxה" "סתyzטזץabcשנבdefגקכghiעיןjklחלךmnoצמםpqrפרףstuד" @@ -626,8 +641,10 @@ int UtcDaliGetClosestCursorIndex(void) visualX05, visualY05, mode05, + false, logicalIndex05, isCharacterHit05}, + {"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" @@ -639,25 +656,42 @@ int UtcDaliGetClosestCursorIndex(void) visualX06, visualY06, mode06, + false, logicalIndex06, isCharacterHit06}, + {"Testing complex characters. Arabic ligatures", "الأَبْجَدِيَّة العَرَبِيَّة", 1u, visualX07, visualY07, mode07, + false, logicalIndex07, isCharacterHit07}, + {"Testing complex characters. Latin ligatures", "fi ligature", 1u, visualX08, visualY08, mode08, + false, logicalIndex08, - isCharacterHit08}}; - const unsigned int numberOfTests = 8u; + isCharacterHit08}, + + {"Testing complex characters. Emoji", + "A👨‍👩‍👧‍👦B", + 1u, + visualX09, + visualY09, + mode09, + true, + logicalIndex09, + isCharacterHit09} + + }; + const unsigned int numberOfTests = 9u; for(unsigned int index = 0; index < numberOfTests; ++index) { diff --git a/automated-tests/src/dali-toolkit/utc-Dali-TextEditor.cpp b/automated-tests/src/dali-toolkit/utc-Dali-TextEditor.cpp index 9d9567b..19c3583 100644 --- a/automated-tests/src/dali-toolkit/utc-Dali-TextEditor.cpp +++ b/automated-tests/src/dali-toolkit/utc-Dali-TextEditor.cpp @@ -5924,4 +5924,83 @@ int UtcDaliToolkitTexteditorParagraphTag(void) application.Render(); END_TEST; +} + +//Handle Emoji clustering for cursor handling +int utcDaliTextEditorClusteredEmojiDeletionBackSpaceKey(void) +{ + ToolkitTestApplication application; + tet_infoline(" utcDaliTextEditorClusteredEmojiDeletionBackSpaceKey "); + TextEditor textEditor = TextEditor::New(); + DALI_TEST_CHECK(textEditor); + + application.GetScene().Add(textEditor); + + // Avoid a crash when core load gl resources. + application.GetGlAbstraction().SetCheckFramebufferStatusResult(GL_FRAMEBUFFER_COMPLETE); + + textEditor.SetProperty(TextEditor::Property::TEXT, "ABC👨‍👩‍👧‍👦XY"); + textEditor.SetProperty(Dali::Toolkit::TextEditor::Property::ENABLE_MARKUP, true); + + // Render and notify + application.SendNotification(); + application.Render(); + + // Set currsor + textEditor.SetProperty(DevelTextEditor::Property::PRIMARY_CURSOR_POSITION, 10); + application.SendNotification(); + application.Render(); + + // Set focus and remove Emoji + textEditor.SetKeyInputFocus(); + application.ProcessEvent(GenerateKey("", "", "", DALI_KEY_BACKSPACE, 0, 0, Integration::KeyEvent::DOWN, "", DEFAULT_DEVICE_NAME, Device::Class::NONE, Device::Subclass::NONE)); + + //Check the changed text and cursor position + DALI_TEST_EQUALS(textEditor.GetProperty(TextEditor::Property::TEXT).Get(), "ABCXY", TEST_LOCATION); + DALI_TEST_EQUALS(textEditor.GetProperty(DevelTextEditor::Property::PRIMARY_CURSOR_POSITION).Get(), 3, TEST_LOCATION); + + // Render and notify + application.SendNotification(); + application.Render(); + + END_TEST; +} + +int utcDaliTextEditorClusteredEmojiDeletionDeleteKey(void) +{ + ToolkitTestApplication application; + tet_infoline(" utcDaliTextEditorClusteredEmojiDeletionDeleteKey "); + TextEditor textEditor = TextEditor::New(); + DALI_TEST_CHECK(textEditor); + + application.GetScene().Add(textEditor); + + // Avoid a crash when core load gl resources. + application.GetGlAbstraction().SetCheckFramebufferStatusResult(GL_FRAMEBUFFER_COMPLETE); + + textEditor.SetProperty(TextEditor::Property::TEXT, "ABC👨‍👩‍👧‍👦XY"); + textEditor.SetProperty(Dali::Toolkit::TextEditor::Property::ENABLE_MARKUP, true); + + // Render and notify + application.SendNotification(); + application.Render(); + + // Set currsor + textEditor.SetProperty(DevelTextEditor::Property::PRIMARY_CURSOR_POSITION, 3); + application.SendNotification(); + application.Render(); + + // Set focus and remove Emoji + textEditor.SetKeyInputFocus(); + application.ProcessEvent(GenerateKey("", "", "", Dali::DevelKey::DALI_KEY_DELETE, 0, 0, Integration::KeyEvent::DOWN, "", DEFAULT_DEVICE_NAME, Device::Class::NONE, Device::Subclass::NONE)); + + //Check the changed text and cursor position + DALI_TEST_EQUALS(textEditor.GetProperty(TextEditor::Property::TEXT).Get(), "ABCXY", TEST_LOCATION); + DALI_TEST_EQUALS(textEditor.GetProperty(DevelTextEditor::Property::PRIMARY_CURSOR_POSITION).Get(), 3, TEST_LOCATION); + + // Render and notify + application.SendNotification(); + application.Render(); + + END_TEST; } \ No newline at end of file diff --git a/automated-tests/src/dali-toolkit/utc-Dali-TextField.cpp b/automated-tests/src/dali-toolkit/utc-Dali-TextField.cpp index 3f5ff1b..48c2bcc 100644 --- a/automated-tests/src/dali-toolkit/utc-Dali-TextField.cpp +++ b/automated-tests/src/dali-toolkit/utc-Dali-TextField.cpp @@ -5319,4 +5319,83 @@ int UtcDaliToolkitTextfieldParagraphTag(void) application.Render(); END_TEST; +} + +//Handle Emoji clustering for cursor handling +int utcDaliTextFieldClusteredEmojiDeletionBackSpaceKey(void) +{ + ToolkitTestApplication application; + tet_infoline(" utcDaliTextFieldClusteredEmojiDeletionBackSpaceKey "); + TextField textField = TextField::New(); + DALI_TEST_CHECK(textField); + + application.GetScene().Add(textField); + + // Avoid a crash when core load gl resources. + application.GetGlAbstraction().SetCheckFramebufferStatusResult(GL_FRAMEBUFFER_COMPLETE); + + textField.SetProperty(TextField::Property::TEXT, "ABC👨‍👩‍👧‍👦XY"); + textField.SetProperty(Dali::Toolkit::TextField::Property::ENABLE_MARKUP, true); + + // Render and notify + application.SendNotification(); + application.Render(); + + // Set currsor + textField.SetProperty(DevelTextField::Property::PRIMARY_CURSOR_POSITION, 10); + application.SendNotification(); + application.Render(); + + // Set focus and remove Emoji + textField.SetKeyInputFocus(); + application.ProcessEvent(GenerateKey("", "", "", DALI_KEY_BACKSPACE, 0, 0, Integration::KeyEvent::DOWN, "", DEFAULT_DEVICE_NAME, Device::Class::NONE, Device::Subclass::NONE)); + + //Check the changed text and cursor position + DALI_TEST_EQUALS(textField.GetProperty(TextField::Property::TEXT).Get(), "ABCXY", TEST_LOCATION); + DALI_TEST_EQUALS(textField.GetProperty(DevelTextField::Property::PRIMARY_CURSOR_POSITION).Get(), 3, TEST_LOCATION); + + // Render and notify + application.SendNotification(); + application.Render(); + + END_TEST; +} + +int utcDaliTextFieldClusteredEmojiDeletionDeleteKey(void) +{ + ToolkitTestApplication application; + tet_infoline(" utcDaliTextFieldClusteredEmojiDeletionDeleteKey "); + TextField textField = TextField::New(); + DALI_TEST_CHECK(textField); + + application.GetScene().Add(textField); + + // Avoid a crash when core load gl resources. + application.GetGlAbstraction().SetCheckFramebufferStatusResult(GL_FRAMEBUFFER_COMPLETE); + + textField.SetProperty(TextField::Property::TEXT, "ABC👨‍👩‍👧‍👦XY"); + textField.SetProperty(Dali::Toolkit::TextField::Property::ENABLE_MARKUP, true); + + // Render and notify + application.SendNotification(); + application.Render(); + + // Set currsor + textField.SetProperty(DevelTextField::Property::PRIMARY_CURSOR_POSITION, 3); + application.SendNotification(); + application.Render(); + + // Set focus and remove Emoji + textField.SetKeyInputFocus(); + application.ProcessEvent(GenerateKey("", "", "", Dali::DevelKey::DALI_KEY_DELETE, 0, 0, Integration::KeyEvent::DOWN, "", DEFAULT_DEVICE_NAME, Device::Class::NONE, Device::Subclass::NONE)); + + //Check the changed text and cursor position + DALI_TEST_EQUALS(textField.GetProperty(TextField::Property::TEXT).Get(), "ABCXY", TEST_LOCATION); + DALI_TEST_EQUALS(textField.GetProperty(DevelTextField::Property::PRIMARY_CURSOR_POSITION).Get(), 3, TEST_LOCATION); + + // Render and notify + application.SendNotification(); + application.Render(); + + END_TEST; } \ No newline at end of file diff --git a/dali-toolkit/internal/file.list b/dali-toolkit/internal/file.list index 7e9c1aa..3fae741 100644 --- a/dali-toolkit/internal/file.list +++ b/dali-toolkit/internal/file.list @@ -140,6 +140,7 @@ SET( toolkit_src_files ${toolkit_src_dir}/text/bidirectional-support.cpp ${toolkit_src_dir}/text/bounded-paragraph-helper-functions.cpp ${toolkit_src_dir}/text/character-set-conversion.cpp + ${toolkit_src_dir}/text/characters-helper-functions.cpp ${toolkit_src_dir}/text/color-segmentation.cpp ${toolkit_src_dir}/text/cursor-helper-functions.cpp ${toolkit_src_dir}/text/glyph-metrics-helper.cpp diff --git a/dali-toolkit/internal/text/characters-helper-functions.cpp b/dali-toolkit/internal/text/characters-helper-functions.cpp new file mode 100644 index 0000000..d89f1fb --- /dev/null +++ b/dali-toolkit/internal/text/characters-helper-functions.cpp @@ -0,0 +1,60 @@ +// FILE HEADER +#include + +// INTERNAL INCLUDES +#include + +namespace Dali +{ +namespace Toolkit +{ +namespace Text +{ +CharacterRun RetrieveClusteredCharactersOfCharacterIndex(const VisualModelPtr& visualModel, + const LogicalModelPtr& logicalModel, + const CharacterIndex& characterIndex) +{ + // Initialization + CharacterRun clusteredCharacters; + clusteredCharacters.characterIndex = characterIndex; + clusteredCharacters.numberOfCharacters = 1u; + + const GlyphIndex* const charactersToGlyphBuffer = visualModel->mCharactersToGlyph.Begin(); + const Length* const charactersPerGlyphBuffer = visualModel->mCharactersPerGlyph.Begin(); + const CharacterIndex* const glyphsToCharacters = visualModel->mGlyphsToCharacters.Begin(); + + GlyphIndex glyphIndex = *(charactersToGlyphBuffer + characterIndex); + Length actualNumberOfCharacters = *(charactersPerGlyphBuffer + glyphIndex); + + if(actualNumberOfCharacters > 1u) + { + const Script script = logicalModel->GetScript(characterIndex); + // Prevents to break the Latin ligatures like fi, ff, or Arabic ï»», ... + // Keep actual index of character as is. Because these characters cannot be clustered. + + if(!HasLigatureMustBreak(script)) + { + clusteredCharacters.numberOfCharacters = actualNumberOfCharacters; + clusteredCharacters.characterIndex = *(glyphsToCharacters + glyphIndex); // firstCharacterIndex + } + } + else + { + while(0u == actualNumberOfCharacters) + { + ++glyphIndex; + actualNumberOfCharacters = *(charactersPerGlyphBuffer + glyphIndex); + } + + clusteredCharacters.characterIndex = *(glyphsToCharacters + glyphIndex); // firstCharacterIndex + clusteredCharacters.numberOfCharacters = actualNumberOfCharacters; + } + + return clusteredCharacters; +} + +} // namespace Text + +} // namespace Toolkit + +} // namespace Dali diff --git a/dali-toolkit/internal/text/characters-helper-functions.h b/dali-toolkit/internal/text/characters-helper-functions.h new file mode 100644 index 0000000..00b1b4e --- /dev/null +++ b/dali-toolkit/internal/text/characters-helper-functions.h @@ -0,0 +1,52 @@ +#ifndef DALI_TOOLKIT_TEXT_CHARACTERS_HELPER_FUNCTIONS_H +#define DALI_TOOLKIT_TEXT_CHARACTERS_HELPER_FUNCTIONS_H + +/* + * Copyright (c) 2022 Samsung Electronics Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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. + * + */ + +// INTERNAL INCLUDES +#include +#include +#include + +namespace Dali +{ +namespace Toolkit +{ +namespace Text +{ +/** + * @brief Retrieve the clustered consecutive characters that contains current character index. + * Found the first index of clustered characters and number of characters. + * + * @param[in] visualModel The visual model. + * @param[in] logicalModel The logical model. + * @param[in] characterIndex The character index. + * + * @return CharacterRun for the clustered characters contains character Index + */ +CharacterRun RetrieveClusteredCharactersOfCharacterIndex(const VisualModelPtr& visualModel, + const LogicalModelPtr& logicalModel, + const CharacterIndex& characterIndex); + +} // namespace Text + +} // namespace Toolkit + +} // namespace Dali + +#endif // DALI_TOOLKIT_TEXT_CHARACTERS_HELPER_FUNCTIONS_H \ No newline at end of file diff --git a/dali-toolkit/internal/text/cursor-helper-functions.cpp b/dali-toolkit/internal/text/cursor-helper-functions.cpp index 3bfc059..d895418 100644 --- a/dali-toolkit/internal/text/cursor-helper-functions.cpp +++ b/dali-toolkit/internal/text/cursor-helper-functions.cpp @@ -22,6 +22,8 @@ #include // INTERNAL INCLUDES +#include +#include #include namespace @@ -458,6 +460,19 @@ CharacterIndex GetClosestCursorIndex(VisualModelPtr visualModel, logicalIndex = (bidiLineFetched ? logicalModel->GetLogicalCursorIndex(visualIndex) : visualIndex); + // Handle Emoji clustering for cursor handling: + // Fixing this case: + // - When there is Emoji contains multi unicodes and it is layoutted at the end of line (LineWrap case , is not new line case) + // - Try to click at the center or at the end of Emoji then the cursor appears inside Emoji + // - Example:"FamilyManWomanGirlBoy 👨‍👩‍👧‍👦" + const Script script = logicalModel->GetScript(logicalIndex); + if(IsOneOfEmojiScripts(script)) + { + //TODO: Use this clustering for Emoji cases only. This needs more testing to generalize to all scripts. + CharacterRun emojiClusteredCharacters = RetrieveClusteredCharactersOfCharacterIndex(visualModel, logicalModel, logicalIndex); + logicalIndex = emojiClusteredCharacters.characterIndex; + } + 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"); diff --git a/dali-toolkit/internal/text/text-controller-text-updater.cpp b/dali-toolkit/internal/text/text-controller-text-updater.cpp index 0b24bc0..f49e953 100644 --- a/dali-toolkit/internal/text/text-controller-text-updater.cpp +++ b/dali-toolkit/internal/text/text-controller-text-updater.cpp @@ -24,6 +24,8 @@ // INTERNAL INCLUDES #include +#include +#include #include #include #include @@ -481,6 +483,7 @@ bool Controller::TextUpdater::RemoveText( ModelPtr& model = impl.mModel; LogicalModelPtr& logicalModel = model->mLogicalModel; + VisualModelPtr& visualModel = model->mVisualModel; DALI_LOG_INFO(gLogFilter, Debug::General, "Controller::RemoveText %p mText.Count() %d cursor %d cursorOffset %d numberOfCharacters %d\n", &controller, logicalModel->mText.Count(), eventData->mPrimaryCursorPosition, cursorOffset, numberOfCharacters); @@ -498,6 +501,25 @@ bool Controller::TextUpdater::RemoveText( cursorIndex = eventData->mPrimaryCursorPosition + cursorOffset; } + //Handle Emoji clustering for cursor handling + // Deletion case: this is handling the deletion cases when the cursor is before or after Emoji + // - Before: when use delete key and cursor is before Emoji (cursorOffset = -1) + // - After: when use backspace key and cursor is after Emoji (cursorOffset = 0) + + const Script script = logicalModel->GetScript(cursorIndex); + if((numberOfCharacters == 1u) && + (IsOneOfEmojiScripts(script))) + { + //TODO: Use this clustering for Emoji cases only. This needs more testing to generalize to all scripts. + CharacterRun emojiClusteredCharacters = RetrieveClusteredCharactersOfCharacterIndex(visualModel, logicalModel, cursorIndex); + Length actualNumberOfCharacters = emojiClusteredCharacters.numberOfCharacters; + + //Set cursorIndex at the first characterIndex of clustred Emoji + cursorIndex = emojiClusteredCharacters.characterIndex; + + numberOfCharacters = actualNumberOfCharacters; + } + if((cursorIndex + numberOfCharacters) > currentText.Count()) { numberOfCharacters = currentText.Count() - cursorIndex; -- 2.7.4