From ded447ca28a294ce011a4c1f25f481b1cc04eefd Mon Sep 17 00:00:00 2001 From: Victor Cebollada Date: Wed, 4 May 2016 09:11:56 +0100 Subject: [PATCH] Text - Move cursor's position related code to a different file. * The goal is create functions that can be easily tested with test cases. Change-Id: I1950365f8a5ce8d74532d738d5820fbc82382810 Signed-off-by: Victor Cebollada --- dali-toolkit/internal/file.list | 1 + .../internal/text/cursor-helper-functions.cpp | 446 +++++++++++++++++++++ .../internal/text/cursor-helper-functions.h | 119 ++++++ .../internal/text/text-controller-impl.cpp | 426 ++------------------ dali-toolkit/internal/text/text-controller-impl.h | 41 +- 5 files changed, 607 insertions(+), 426 deletions(-) create mode 100644 dali-toolkit/internal/text/cursor-helper-functions.cpp create mode 100644 dali-toolkit/internal/text/cursor-helper-functions.h diff --git a/dali-toolkit/internal/file.list b/dali-toolkit/internal/file.list index d043b79..7fba3eb 100644 --- a/dali-toolkit/internal/file.list +++ b/dali-toolkit/internal/file.list @@ -92,6 +92,7 @@ toolkit_src_files = \ $(toolkit_src_dir)/text/character-set-conversion.cpp \ $(toolkit_src_dir)/text/clipping/text-clipper.cpp \ $(toolkit_src_dir)/text/color-segmentation.cpp \ + $(toolkit_src_dir)/text/cursor-helper-functions.cpp \ $(toolkit_src_dir)/text/glyph-metrics-helper.cpp \ $(toolkit_src_dir)/text/logical-model-impl.cpp \ $(toolkit_src_dir)/text/markup-processor.cpp \ diff --git a/dali-toolkit/internal/text/cursor-helper-functions.cpp b/dali-toolkit/internal/text/cursor-helper-functions.cpp new file mode 100644 index 0000000..1bce75a --- /dev/null +++ b/dali-toolkit/internal/text/cursor-helper-functions.cpp @@ -0,0 +1,446 @@ +/* + * 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. + * + */ + +// FILE HEADER +#include + +// EXTERNAL INCLUDES +#include + +// INTERNAL INCLUDES +#include + +namespace +{ + +#if defined(DEBUG_ENABLED) + Debug::Filter* gLogFilter = Debug::Filter::New(Debug::NoLogging, true, "LOG_TEXT_CONTROLS"); +#endif + +} // namespace + +namespace Dali +{ + +namespace Toolkit +{ + +namespace Text +{ + +LineIndex GetClosestLine( VisualModelPtr visualModel, + float visualY ) +{ + float totalHeight = 0.f; + LineIndex lineIndex = 0u; + + const Vector& lines = visualModel->mLines; + for( LineIndex endLine = lines.Count(); + lineIndex < endLine; + ++lineIndex ) + { + const LineRun& lineRun = lines[lineIndex]; + totalHeight += lineRun.ascender + -lineRun.descender; + if( visualY < totalHeight ) + { + return lineIndex; + } + } + + if( lineIndex == 0 ) + { + return 0; + } + + return lineIndex-1; +} + +CharacterIndex GetClosestCursorIndex( VisualModelPtr visualModel, + LogicalModelPtr logicalModel, + MetricsPtr metrics, + float visualX, + float visualY ) +{ + DALI_LOG_INFO( gLogFilter, Debug::Verbose, "GetClosestCursorIndex, closest visualX %f visualY %f\n", visualX, visualY ); + + CharacterIndex logicalIndex = 0u; + + const Length numberOfGlyphs = visualModel->mGlyphs.Count(); + const Length numberOfLines = visualModel->mLines.Count(); + if( ( 0 == numberOfGlyphs ) || + ( 0 == numberOfLines ) ) + { + return logicalIndex; + } + + // Find which line is closest. + const LineIndex lineIndex = Text::GetClosestLine( visualModel, + visualY ); + const LineRun& line = visualModel->mLines[lineIndex]; + + // Get the positions of the glyphs. + const Vector& positions = visualModel->mGlyphPositions; + const Vector2* const positionsBuffer = positions.Begin(); + + // Get the character to glyph conversion table. + const GlyphIndex* const charactersToGlyphBuffer = visualModel->mCharactersToGlyph.Begin(); + + // Get the glyphs per character table. + const Length* const glyphsPerCharacterBuffer = visualModel->mGlyphsPerCharacter.Begin(); + + // Get the glyph's info buffer. + const GlyphInfo* const glyphInfoBuffer = visualModel->mGlyphs.Begin(); + + const CharacterIndex startCharacter = line.characterRun.characterIndex; + const CharacterIndex endCharacter = line.characterRun.characterIndex + line.characterRun.numberOfCharacters; + DALI_ASSERT_DEBUG( endCharacter <= logicalModel->mText.Count() && "Invalid line info" ); + + // Whether this line is a bidirectional line. + const bool bidiLineFetched = logicalModel->FetchBidirectionalLineInfo( startCharacter ); + + // 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 ) + { + // 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 ); + + // The number of glyphs for that character + const Length numberOfGlyphs = *( glyphsPerCharacterBuffer + characterLogicalOrderIndex ); + ++numberOfCharacters; + + + if( 0u != numberOfGlyphs ) + { + // Get the first character/glyph of the group of glyphs. + const CharacterIndex firstVisualCharacterIndex = 1u + visualIndex - numberOfCharacters; + const CharacterIndex firstLogicalCharacterIndex = ( bidiLineFetched ? logicalModel->GetLogicalCharacterIndex( firstVisualCharacterIndex ) : firstVisualCharacterIndex ); + const GlyphIndex firstLogicalGlyphIndex = *( charactersToGlyphBuffer + firstLogicalCharacterIndex ); + + // Get the metrics for the group of glyphs. + GlyphMetrics glyphMetrics; + GetGlyphsMetrics( firstLogicalGlyphIndex, + numberOfGlyphs, + glyphMetrics, + glyphInfoBuffer, + metrics ); + + // Get the position of the first glyph. + const Vector2& position = *( positionsBuffer + firstLogicalGlyphIndex ); + + // Whether the glyph can be split, like Latin ligatures fi, ff or Arabic ï»». + const 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 ) + { + // Find the mid-point of the area containing the glyph + const float glyphCenter = -glyphMetrics.xBearing + position.x + ( static_cast( index ) + 0.5f ) * glyphAdvance; + + if( visualX < glyphCenter ) + { + matched = true; + break; + } + } + + if( matched ) + { + visualIndex = firstVisualCharacterIndex + index; + break; + } + + numberOfCharacters = 0u; + } + + } + + // Return the logical position of the cursor in characters. + + if( !matched ) + { + visualIndex = endCharacter; + } + + 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" ); + + return logicalIndex; +} + + +void GetCursorPosition( VisualModelPtr visualModel, + LogicalModelPtr logicalModel, + MetricsPtr metrics, + 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; + + // '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; + + 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 ); + } + + // Get the line where the character is laid-out. + const LineRun* const modelLines = visualModel->mLines.Begin(); + + const LineIndex lineIndex = visualModel->GetLineOfCharacter( characterIndex ); + const LineRun& line = *( modelLines + lineIndex ); + + // Get the paragraph's direction. + const CharacterDirection isRightToLeftParagraph = line.direction; + + // Check whether there is an alternative position: + + cursorInfo.isSecondaryCursor = ( !isLastPosition && ( isCurrentRightToLeft != isNextRightToLeft ) ) || + ( isLastPosition && ( isRightToLeftParagraph != isCurrentRightToLeft ) ); + + // Set the line offset and height. + cursorInfo.lineOffset = 0.f; + cursorInfo.lineHeight = line.ascender + -line.descender; + + // Calculate the primary cursor. + + CharacterIndex index = characterIndex; + if( cursorInfo.isSecondaryCursor ) + { + // If there is a secondary position, the primary cursor may be in a different place than the logical index. + + if( isLastPosition ) + { + // 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. + + // TODO: check for more than one line! + index = isRightToLeftParagraph ? line.characterRun.characterIndex : line.characterRun.characterIndex + line.characterRun.numberOfCharacters - 1u; + index = logicalModel->GetLogicalCharacterIndex( index ); + } + else + { + index = ( isRightToLeftParagraph == isCurrentRightToLeft ) ? characterIndex : 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(); + + // 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 ); + + bool isCurrentRightToLeft = false; + if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right. + { + isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + index ); + } + + Length numberOfGlyphAdvance = ( isFirstPosition ? 0u : 1u ) + characterIndex - firstIndex; + if( isCurrentRightToLeft ) + { + numberOfGlyphAdvance = primaryNumberOfCharacters - numberOfGlyphAdvance; + } + + glyphAdvance = static_cast( numberOfGlyphAdvance ) * glyphMetrics.advance / static_cast( primaryNumberOfCharacters ); + } + + // Get the glyph position and x bearing. + const Vector2& primaryPosition = *( glyphPositionsBuffer + primaryGlyphIndex ); + + // Set the primary cursor's height. + cursorInfo.primaryCursorHeight = cursorInfo.isSecondaryCursor ? 0.5f * glyphMetrics.fontHeight : glyphMetrics.fontHeight; + + // Set the primary cursor's position. + cursorInfo.primaryPosition.x = -glyphMetrics.xBearing + primaryPosition.x + glyphAdvance; + cursorInfo.primaryPosition.y = line.ascender - glyphMetrics.ascender; + + // Calculate the secondary cursor. + + if( cursorInfo.isSecondaryCursor ) + { + // Set the secondary cursor's height. + cursorInfo.secondaryCursorHeight = 0.5f * glyphMetrics.fontHeight; + + CharacterIndex index = characterIndex; + if( !isLastPosition ) + { + index = ( isRightToLeftParagraph == isCurrentRightToLeft ) ? nextCharacterIndex : characterIndex; + } + + const GlyphIndex secondaryGlyphIndex = *( charactersToGlyphBuffer + index ); + const Length secondaryNumberOfGlyphs = *( glyphsPerCharacterBuffer + index ); + + const Vector2& secondaryPosition = *( glyphPositionsBuffer + secondaryGlyphIndex ); + + GetGlyphsMetrics( secondaryGlyphIndex, + secondaryNumberOfGlyphs, + glyphMetrics, + glyphInfoBuffer, + metrics ); + + // 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 ); + } +} + +void FindSelectionIndices( VisualModelPtr visualModel, + LogicalModelPtr logicalModel, + MetricsPtr metrics, + float visualX, + float visualY, + CharacterIndex& startIndex, + CharacterIndex& endIndex ) +{ + CharacterIndex hitCharacter = Text::GetClosestCursorIndex( visualModel, + logicalModel, + metrics, + visualX, + visualY ); + DALI_ASSERT_DEBUG( hitCharacter <= logicalModel->mText.Count() && "GetClosestCursorIndex returned out of bounds index" ); + + if( logicalModel->mText.Count() == 0 ) + { + return; // if model empty + } + + if( hitCharacter >= logicalModel->mText.Count() ) + { + // Closest hit character is the last character. + if( hitCharacter == logicalModel->mText.Count() ) + { + hitCharacter--; //Hit character index set to last character in logical model + } + else + { + // hitCharacter is out of bounds + return; + } + } + + startIndex = hitCharacter; + endIndex = hitCharacter; + bool isHitCharacterWhitespace = TextAbstraction::IsWhiteSpace( logicalModel->mText[hitCharacter] ); + + // Find the start and end of the text + for( startIndex = hitCharacter; startIndex > 0; --startIndex ) + { + if( isHitCharacterWhitespace != TextAbstraction::IsWhiteSpace( logicalModel->mText[ startIndex-1 ] ) ) + { + break; + } + } + const CharacterIndex pastTheEnd = logicalModel->mText.Count(); + for( endIndex = hitCharacter + 1u; endIndex < pastTheEnd; ++endIndex ) + { + if( isHitCharacterWhitespace != TextAbstraction::IsWhiteSpace( logicalModel->mText[ endIndex ] ) ) + { + break; + } + } +} + +} // namespace Text + +} // namespace Toolkit + +} // namespace Dali diff --git a/dali-toolkit/internal/text/cursor-helper-functions.h b/dali-toolkit/internal/text/cursor-helper-functions.h new file mode 100644 index 0000000..7ce02bf --- /dev/null +++ b/dali-toolkit/internal/text/cursor-helper-functions.h @@ -0,0 +1,119 @@ +#ifndef __DALI_TOOLKIT_TEXT_CURSOR_HELPER_FUNCTIONS_H__ +#define __DALI_TOOLKIT_TEXT_CURSOR_HELPER_FUNCTIONS_H__ + +/* + * 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. + * + */ + +// INTERNAL INCLUDES +#include +#include +#include + +namespace Dali +{ + +namespace Toolkit +{ + +namespace Text +{ + +struct CursorInfo +{ + CursorInfo() + : primaryPosition(), + secondaryPosition(), + lineOffset( 0.f ), + lineHeight( 0.f ), + primaryCursorHeight( 0.f ), + secondaryCursorHeight( 0.f ), + isSecondaryCursor( false ) + {} + + ~CursorInfo() + {} + + Vector2 primaryPosition; ///< The primary cursor's position. + Vector2 secondaryPosition; ///< The secondary cursor's position. + float lineOffset; ///< The vertical offset where the line containing the cursor starts. + float lineHeight; ///< The height of the line where the cursor is placed. + float primaryCursorHeight; ///< The primary cursor's height. + float secondaryCursorHeight; ///< The secondary cursor's height. + bool isSecondaryCursor; ///< Whether the secondary cursor is valid. +}; + +/** + * @brief Retrieves the closest line for a given touch point. + * + * It returns the first line if the touch point is above the text and the last line if the touch point is below. + * + * @return A line index. + */ +LineIndex GetClosestLine( VisualModelPtr visualModel, + float visualY ); + +/** + * @brief Retrieves the cursor's logical position for a given touch point x,y + * + * @param[in] visualModel The visual model. + * @param[in] logicalModel The logical model. + * @param[in] metrics A wrapper around FontClient used to get metrics. + * @param[in] visualX The touch point x. + * @param[in] visualY The touch point y. + * + * @return The logical cursor position (in characters). 0 is just before the first character, a value equal to the number of characters is just after the last character. + */ +CharacterIndex GetClosestCursorIndex( VisualModelPtr visualModel, + LogicalModelPtr logicalModel, + MetricsPtr metrics, + float visualX, + float visualY ); + + +/** + * @brief Calculates the cursor's position for a given character index in the logical order. + * + * It retrieves as well the line's height and the cursor's height and + * if there is a valid alternative cursor, its position and height. + * + * @param[in] logical The logical cursor position (in characters). 0 is just before the first character, a value equal to the number of characters is just after the last character. + * @param[out] cursorInfo The line's height, the cursor's height, the cursor's position and whether there is an alternative cursor. + */ +void GetCursorPosition( VisualModelPtr visualModel, + LogicalModelPtr logicalModel, + MetricsPtr metrics, + CharacterIndex logical, + CursorInfo& cursorInfo ); + +/** + * + */ +void FindSelectionIndices( VisualModelPtr visualModel, + LogicalModelPtr logicalModel, + MetricsPtr metrics, + float visualX, + float visualY, + CharacterIndex& startIndex, + CharacterIndex& endIndex ); + +} // namespace Text + +} // namespace Toolkit + +} // namespace Dali + +#endif // __DALI_TOOLKIT_TEXT_CURSOR_HELPER_FUNCTIONS_H__ diff --git a/dali-toolkit/internal/text/text-controller-impl.cpp b/dali-toolkit/internal/text/text-controller-impl.cpp index 29b5718..7938931 100644 --- a/dali-toolkit/internal/text/text-controller-impl.cpp +++ b/dali-toolkit/internal/text/text-controller-impl.cpp @@ -26,7 +26,7 @@ #include #include #include -#include +#include #include #include #include @@ -1001,8 +1001,11 @@ void Controller::Impl::OnTapEvent( const Event& event ) const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x; const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y; - mEventData->mPrimaryCursorPosition = GetClosestCursorIndex( xPosition, - yPosition ); + mEventData->mPrimaryCursorPosition = Text::GetClosestCursorIndex( mVisualModel, + mLogicalModel, + mMetrics, + xPosition, + yPosition ); // When the cursor position is changing, delay cursor blinking mEventData->mDecorator->DelayCursorBlink(); @@ -1093,7 +1096,11 @@ void Controller::Impl::OnHandleEvent( const Event& event ) const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x; const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y; - const CharacterIndex handleNewPosition = GetClosestCursorIndex( xPosition, yPosition ); + const CharacterIndex handleNewPosition = Text::GetClosestCursorIndex( mVisualModel, + mLogicalModel, + mMetrics, + xPosition, + yPosition ); if( Event::GRAB_HANDLE_EVENT == event.type ) { @@ -1140,7 +1147,11 @@ void Controller::Impl::OnHandleEvent( const Event& event ) const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x; const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y; - handlePosition = GetClosestCursorIndex( xPosition, yPosition ); + handlePosition = Text::GetClosestCursorIndex( mVisualModel, + mLogicalModel, + mMetrics, + xPosition, + yPosition ); } if( Event::GRAB_HANDLE_EVENT == event.type ) @@ -1227,8 +1238,11 @@ void Controller::Impl::OnHandleEvent( const Event& event ) // Get the new handle position. // The grab handle's position is in decorator coords. Need to transforms to text coords. - const CharacterIndex handlePosition = GetClosestCursorIndex( position.x - mEventData->mScrollPosition.x - mAlignmentOffset.x, - position.y - mEventData->mScrollPosition.y - mAlignmentOffset.y ); + const CharacterIndex handlePosition = Text::GetClosestCursorIndex( mVisualModel, + mLogicalModel, + mMetrics, + position.x - mEventData->mScrollPosition.x - mAlignmentOffset.x, + position.y - mEventData->mScrollPosition.y - mAlignmentOffset.y ); mEventData->mUpdateCursorPosition = mEventData->mPrimaryCursorPosition != handlePosition; mEventData->mScrollAfterUpdatePosition = mEventData->mUpdateCursorPosition; @@ -1249,8 +1263,11 @@ void Controller::Impl::OnHandleEvent( const Event& event ) // Get the new handle position. // The selection handle's position is in decorator coords. Need to transforms to text coords. - const CharacterIndex handlePosition = GetClosestCursorIndex( position.x - mEventData->mScrollPosition.x - mAlignmentOffset.x, - position.y - mEventData->mScrollPosition.y - mAlignmentOffset.y ); + const CharacterIndex handlePosition = Text::GetClosestCursorIndex( mVisualModel, + mLogicalModel, + mMetrics, + position.x - mEventData->mScrollPosition.x - mAlignmentOffset.x, + position.y - mEventData->mScrollPosition.y - mAlignmentOffset.y ); if( leftSelectionHandleEvent ) { @@ -1603,7 +1620,13 @@ void Controller::Impl::RepositionSelectionHandles( float visualX, float visualY // Find which word was selected CharacterIndex selectionStart( 0 ); CharacterIndex selectionEnd( 0 ); - FindSelectionIndices( visualX, visualY, selectionStart, selectionEnd ); + FindSelectionIndices( mVisualModel, + mLogicalModel, + mMetrics, + visualX, + visualY, + selectionStart, + selectionEnd ); DALI_LOG_INFO( gLogFilter, Debug::Verbose, "%p selectionStart %d selectionEnd %d\n", this, selectionStart, selectionEnd ); if( selectionStart == selectionEnd ) @@ -1837,210 +1860,9 @@ void Controller::Impl::ChangeState( EventData::State newState ) } } -LineIndex Controller::Impl::GetClosestLine( float y ) const -{ - float totalHeight = 0.f; - LineIndex lineIndex = 0u; - - const Vector& lines = mVisualModel->mLines; - for( LineIndex endLine = lines.Count(); - lineIndex < endLine; - ++lineIndex ) - { - const LineRun& lineRun = lines[lineIndex]; - totalHeight += lineRun.ascender + -lineRun.descender; - if( y < totalHeight ) - { - return lineIndex; - } - } - - if( lineIndex == 0 ) - { - return 0; - } - - return lineIndex-1; -} - -void Controller::Impl::FindSelectionIndices( float visualX, float visualY, CharacterIndex& startIndex, CharacterIndex& endIndex ) -{ - CharacterIndex hitCharacter = GetClosestCursorIndex( visualX, visualY ); - DALI_ASSERT_DEBUG( hitCharacter <= mLogicalModel->mText.Count() && "GetClosestCursorIndex returned out of bounds index" ); - - if( mLogicalModel->mText.Count() == 0 ) - { - return; // if model empty - } - - if( hitCharacter >= mLogicalModel->mText.Count() ) - { - // Closest hit character is the last character. - if( hitCharacter == mLogicalModel->mText.Count() ) - { - hitCharacter--; //Hit character index set to last character in logical model - } - else - { - // hitCharacter is out of bounds - return; - } - } - - startIndex = hitCharacter; - endIndex = hitCharacter; - bool isHitCharacterWhitespace = TextAbstraction::IsWhiteSpace( mLogicalModel->mText[hitCharacter] ); - - // Find the start and end of the text - for( startIndex = hitCharacter; startIndex > 0; --startIndex ) - { - if( isHitCharacterWhitespace != TextAbstraction::IsWhiteSpace( mLogicalModel->mText[ startIndex-1 ] ) ) - { - break; - } - } - const CharacterIndex pastTheEnd = mLogicalModel->mText.Count(); - for( endIndex = hitCharacter + 1u; endIndex < pastTheEnd; ++endIndex ) - { - if( isHitCharacterWhitespace != TextAbstraction::IsWhiteSpace( mLogicalModel->mText[ endIndex ] ) ) - { - break; - } - } -} - -CharacterIndex Controller::Impl::GetClosestCursorIndex( float visualX, - float visualY ) -{ - DALI_LOG_INFO( gLogFilter, Debug::Verbose, "GetClosestCursorIndex %p closest visualX %f visualY %f\n", this, visualX, visualY ); - - if( NULL == mEventData ) - { - // Nothing to do if there is no text input. - return 0u; - } - - CharacterIndex logicalIndex = 0u; - - const Length numberOfGlyphs = mVisualModel->mGlyphs.Count(); - const Length numberOfLines = mVisualModel->mLines.Count(); - if( ( 0 == numberOfGlyphs ) || - ( 0 == numberOfLines ) ) - { - return logicalIndex; - } - - // Find which line is closest. - const LineIndex lineIndex = GetClosestLine( visualY ); - const LineRun& line = mVisualModel->mLines[lineIndex]; - - // Get the positions of the glyphs. - const Vector& positions = mVisualModel->mGlyphPositions; - const Vector2* const positionsBuffer = positions.Begin(); - - // Get the character to glyph conversion table. - const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin(); - - // Get the glyphs per character table. - const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin(); - - // Get the glyph's info buffer. - const GlyphInfo* const glyphInfoBuffer = mVisualModel->mGlyphs.Begin(); - - const CharacterIndex startCharacter = line.characterRun.characterIndex; - const CharacterIndex endCharacter = line.characterRun.characterIndex + line.characterRun.numberOfCharacters; - DALI_ASSERT_DEBUG( endCharacter <= mLogicalModel->mText.Count() && "Invalid line info" ); - - // Whether this line is a bidirectional line. - const bool bidiLineFetched = mLogicalModel->FetchBidirectionalLineInfo( startCharacter ); - - // 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 ) - { - // The character in logical order. - const CharacterIndex characterLogicalOrderIndex = ( bidiLineFetched ? mLogicalModel->GetLogicalCharacterIndex( visualIndex ) : visualIndex ); - - // Get the script of the character. - const Script script = mLogicalModel->GetScript( characterLogicalOrderIndex ); - - // The number of glyphs for that character - const Length numberOfGlyphs = *( glyphsPerCharacterBuffer + characterLogicalOrderIndex ); - ++numberOfCharacters; - - - if( 0u != numberOfGlyphs ) - { - // Get the first character/glyph of the group of glyphs. - const CharacterIndex firstVisualCharacterIndex = 1u + visualIndex - numberOfCharacters; - const CharacterIndex firstLogicalCharacterIndex = ( bidiLineFetched ? mLogicalModel->GetLogicalCharacterIndex( firstVisualCharacterIndex ) : firstVisualCharacterIndex ); - const GlyphIndex firstLogicalGlyphIndex = *( charactersToGlyphBuffer + firstLogicalCharacterIndex ); - - // Get the metrics for the group of glyphs. - GlyphMetrics glyphMetrics; - GetGlyphsMetrics( firstLogicalGlyphIndex, - numberOfGlyphs, - glyphMetrics, - glyphInfoBuffer, - mMetrics ); - - // Get the position of the first glyph. - const Vector2& position = *( positionsBuffer + firstLogicalGlyphIndex ); - - // Whether the glyph can be split, like Latin ligatures fi, ff or Arabic ï»». - const 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 ) - { - // Find the mid-point of the area containing the glyph - const float glyphCenter = -glyphMetrics.xBearing + position.x + ( static_cast( index ) + 0.5f ) * glyphAdvance; - - if( visualX < glyphCenter ) - { - matched = true; - break; - } - } - - if( matched ) - { - visualIndex = firstVisualCharacterIndex + index; - break; - } - - numberOfCharacters = 0u; - } - - } - - // Return the logical position of the cursor in characters. - - if( !matched ) - { - visualIndex = endCharacter; - } - - logicalIndex = ( bidiLineFetched ? mLogicalModel->GetLogicalCursorIndex( visualIndex ) : visualIndex ); - DALI_LOG_INFO( gLogFilter, Debug::Verbose, "%p closest visualIndex %d logicalIndex %d\n", this, visualIndex, logicalIndex ); - - DALI_ASSERT_DEBUG( ( logicalIndex <= mLogicalModel->mText.Count() && logicalIndex >= 0 ) && "GetClosestCursorIndex - Out of bounds index" ); - - return logicalIndex; -} - void Controller::Impl::GetCursorPosition( CharacterIndex logical, CursorInfo& cursorInfo ) { - // TODO: Check for multiline with \n, etc... - - const Length numberOfCharacters = mLogicalModel->mText.Count(); if( !IsShowingRealText() ) { // Do not want to use the place-holder text to set the cursor position. @@ -2095,183 +1917,11 @@ void Controller::Impl::GetCursorPosition( CharacterIndex logical, return; } - // 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; - - // '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 != mLogicalModel->mCharacterDirections.Count() ) ? mLogicalModel->mCharacterDirections.Begin() : NULL; - - 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 ); - } - - // Get the line where the character is laid-out. - const LineRun* const modelLines = mVisualModel->mLines.Begin(); - - const LineIndex lineIndex = mVisualModel->GetLineOfCharacter( characterIndex ); - const LineRun& line = *( modelLines + lineIndex ); - - // Get the paragraph's direction. - const CharacterDirection isRightToLeftParagraph = line.direction; - - // Check whether there is an alternative position: - - cursorInfo.isSecondaryCursor = ( !isLastPosition && ( isCurrentRightToLeft != isNextRightToLeft ) ) || - ( isLastPosition && ( isRightToLeftParagraph != isCurrentRightToLeft ) ); - - // Set the line offset and height. - cursorInfo.lineOffset = 0.f; - cursorInfo.lineHeight = line.ascender + -line.descender; - - // Calculate the primary cursor. - - CharacterIndex index = characterIndex; - if( cursorInfo.isSecondaryCursor ) - { - // If there is a secondary position, the primary cursor may be in a different place than the logical index. - - if( isLastPosition ) - { - // 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. - - // TODO: check for more than one line! - index = isRightToLeftParagraph ? line.characterRun.characterIndex : line.characterRun.characterIndex + line.characterRun.numberOfCharacters - 1u; - index = mLogicalModel->GetLogicalCharacterIndex( index ); - } - else - { - index = ( isRightToLeftParagraph == isCurrentRightToLeft ) ? characterIndex : nextCharacterIndex; - } - } - - const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin(); - const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin(); - const Length* const charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin(); - const CharacterIndex* const glyphsToCharactersBuffer = mVisualModel->mGlyphsToCharacters.Begin(); - const Vector2* const glyphPositionsBuffer = mVisualModel->mGlyphPositions.Begin(); - const GlyphInfo* const glyphInfoBuffer = mVisualModel->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, - mMetrics ); - - // 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 ); - - bool isCurrentRightToLeft = false; - if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right. - { - isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + index ); - } - - Length numberOfGlyphAdvance = ( isFirstPosition ? 0u : 1u ) + characterIndex - firstIndex; - if( isCurrentRightToLeft ) - { - numberOfGlyphAdvance = primaryNumberOfCharacters - numberOfGlyphAdvance; - } - - glyphAdvance = static_cast( numberOfGlyphAdvance ) * glyphMetrics.advance / static_cast( primaryNumberOfCharacters ); - } - - // Get the glyph position and x bearing. - const Vector2& primaryPosition = *( glyphPositionsBuffer + primaryGlyphIndex ); - - // Set the primary cursor's height. - cursorInfo.primaryCursorHeight = cursorInfo.isSecondaryCursor ? 0.5f * glyphMetrics.fontHeight : glyphMetrics.fontHeight; - - // Set the primary cursor's position. - cursorInfo.primaryPosition.x = -glyphMetrics.xBearing + primaryPosition.x + glyphAdvance; - cursorInfo.primaryPosition.y = line.ascender - glyphMetrics.ascender; - - // Calculate the secondary cursor. - - if( cursorInfo.isSecondaryCursor ) - { - // Set the secondary cursor's height. - cursorInfo.secondaryCursorHeight = 0.5f * glyphMetrics.fontHeight; - - CharacterIndex index = characterIndex; - if( !isLastPosition ) - { - index = ( isRightToLeftParagraph == isCurrentRightToLeft ) ? nextCharacterIndex : characterIndex; - } - - const GlyphIndex secondaryGlyphIndex = *( charactersToGlyphBuffer + index ); - const Length secondaryNumberOfGlyphs = *( glyphsPerCharacterBuffer + index ); - - const Vector2& secondaryPosition = *( glyphPositionsBuffer + secondaryGlyphIndex ); - - GetGlyphsMetrics( secondaryGlyphIndex, - secondaryNumberOfGlyphs, - glyphMetrics, - glyphInfoBuffer, - mMetrics ); - - // 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 ); - } + Text::GetCursorPosition( mVisualModel, + mLogicalModel, + mMetrics, + logical, + cursorInfo ); if( LayoutEngine::MULTI_LINE_BOX == mLayoutEngine.GetLayout() ) { diff --git a/dali-toolkit/internal/text/text-controller-impl.h b/dali-toolkit/internal/text/text-controller-impl.h index 5c1ed0c..c996a1f 100644 --- a/dali-toolkit/internal/text/text-controller-impl.h +++ b/dali-toolkit/internal/text/text-controller-impl.h @@ -39,6 +39,9 @@ namespace Toolkit namespace Text { +//Forward declarations +struct CursorInfo; + struct Event { // Used to queue input events until DoRelayout() @@ -76,30 +79,6 @@ struct Event Param p3; }; -struct CursorInfo -{ - CursorInfo() - : primaryPosition(), - secondaryPosition(), - lineOffset( 0.f ), - lineHeight( 0.f ), - primaryCursorHeight( 0.f ), - secondaryCursorHeight( 0.f ), - isSecondaryCursor( false ) - {} - - ~CursorInfo() - {} - - Vector2 primaryPosition; ///< The primary cursor's position. - Vector2 secondaryPosition; ///< The secondary cursor's position. - float lineOffset; ///< The vertical offset where the line containing the cursor starts. - float lineHeight; ///< The height of the line where the cursor is placed. - float primaryCursorHeight; ///< The primary cursor's height. - float secondaryCursorHeight; ///< The secondary cursor's height. - bool isSecondaryCursor; ///< Whether the secondary cursor is valid. -}; - struct EventData { enum State @@ -540,20 +519,6 @@ struct Controller::Impl void SetPopupButtons(); void ChangeState( EventData::State newState ); - LineIndex GetClosestLine( float y ) const; - - void FindSelectionIndices( float visualX, float visualY, CharacterIndex& startIndex, CharacterIndex& endIndex ); - - /** - * @brief Retrieves the cursor's logical position for a given touch point x,y - * - * @param[in] visualX The touch point x. - * @param[in] visualY The touch point y. - * - * @return The logical cursor position (in characters). 0 is just before the first character, a value equal to the number of characters is just after the last character. - */ - CharacterIndex GetClosestCursorIndex( float visualX, - float visualY ); /** * @brief Calculates the cursor's position for a given character index in the logical order. -- 2.7.4