X-Git-Url: http://review.tizen.org/git/?a=blobdiff_plain;f=dali-toolkit%2Finternal%2Ftext%2Fcursor-helper-functions.cpp;h=1fbca0d907aacafaa7b89b23eebdfd582b681432;hb=63f9b5207c2794cdc460d587723be89585872a51;hp=2b5e32858e403d3f7a9b1c64b0d214a91d06948c;hpb=dda9d7ca30d6f5c42759647df03a13e52c76228a;p=platform%2Fcore%2Fuifw%2Fdali-toolkit.git diff --git a/dali-toolkit/internal/text/cursor-helper-functions.cpp b/dali-toolkit/internal/text/cursor-helper-functions.cpp index 2b5e328..1fbca0d 100644 --- a/dali-toolkit/internal/text/cursor-helper-functions.cpp +++ b/dali-toolkit/internal/text/cursor-helper-functions.cpp @@ -33,6 +33,97 @@ namespace const Dali::Toolkit::Text::CharacterDirection LTR = false; ///< Left To Right direction. +struct FindWordData +{ + FindWordData( const Dali::Toolkit::Text::Character* const textBuffer, + Dali::Toolkit::Text::Length totalNumberOfCharacters, + Dali::Toolkit::Text::CharacterIndex hitCharacter, + bool isWhiteSpace, + bool isNewParagraph ) + : textBuffer( textBuffer ), + totalNumberOfCharacters( totalNumberOfCharacters ), + hitCharacter( hitCharacter ), + foundIndex( 0u ), + isWhiteSpace( isWhiteSpace ), + isNewParagraph( isNewParagraph ) + {} + + ~FindWordData() + {} + + const Dali::Toolkit::Text::Character* const textBuffer; + Dali::Toolkit::Text::Length totalNumberOfCharacters; + Dali::Toolkit::Text::CharacterIndex hitCharacter; + Dali::Toolkit::Text::CharacterIndex foundIndex; + bool isWhiteSpace : 1u; + bool isNewParagraph : 1u; +}; + +bool IsWhiteSpaceOrNewParagraph( Dali::Toolkit::Text::Character character, + bool isHitWhiteSpace, + bool isHitWhiteSpaceOrNewParagraph ) +{ + bool isWhiteSpaceOrNewParagraph = false; + if( isHitWhiteSpaceOrNewParagraph ) + { + if( isHitWhiteSpace ) + { + // Whether the current character is a white space. Note a new paragraph character is a white space as well but here is not wanted. + isWhiteSpaceOrNewParagraph = Dali::TextAbstraction::IsWhiteSpace( character ) && !Dali::TextAbstraction::IsNewParagraph( character ); + } + else + { + // Whether the current character is a new paragraph character. + isWhiteSpaceOrNewParagraph = Dali::TextAbstraction::IsNewParagraph( character ); + } + } + else + { + // Whether the current character is a white space or a new paragraph character (note the new paragraph character is a white space as well). + isWhiteSpaceOrNewParagraph = Dali::TextAbstraction::IsWhiteSpace( character ); + } + + return isWhiteSpaceOrNewParagraph; +} + +void FindStartOfWord( FindWordData& data ) +{ + const bool isHitWhiteSpaceOrNewParagraph = data.isWhiteSpace || data.isNewParagraph; + + for( data.foundIndex = data.hitCharacter; data.foundIndex > 0; --data.foundIndex ) + { + const Dali::Toolkit::Text::Character character = *( data.textBuffer + data.foundIndex - 1u ); + + const bool isWhiteSpaceOrNewParagraph = IsWhiteSpaceOrNewParagraph( character, + data.isWhiteSpace, + isHitWhiteSpaceOrNewParagraph ); + + if( isHitWhiteSpaceOrNewParagraph != isWhiteSpaceOrNewParagraph ) + { + break; + } + } +} + +void FindEndOfWord( FindWordData& data ) +{ + const bool isHitWhiteSpaceOrNewParagraph = data.isWhiteSpace || data.isNewParagraph; + + for( data.foundIndex = data.hitCharacter + 1u; data.foundIndex < data.totalNumberOfCharacters; ++data.foundIndex ) + { + const Dali::Toolkit::Text::Character character = *( data.textBuffer + data.foundIndex ); + + const bool isWhiteSpaceOrNewParagraph = IsWhiteSpaceOrNewParagraph( character, + data.isWhiteSpace, + isHitWhiteSpaceOrNewParagraph ); + + if( isHitWhiteSpaceOrNewParagraph != isWhiteSpaceOrNewParagraph ) + { + break; + } + } +} + } //namespace namespace Dali @@ -107,10 +198,10 @@ CharacterIndex GetClosestCursorIndex( VisualModelPtr visualModel, CharacterIndex logicalIndex = 0u; - const Length numberOfGlyphs = visualModel->mGlyphs.Count(); - const Length numberOfLines = visualModel->mLines.Count(); - if( ( 0 == numberOfGlyphs ) || - ( 0 == numberOfLines ) ) + const Length totalNumberOfGlyphs = visualModel->mGlyphs.Count(); + const Length totalNumberOfLines = visualModel->mLines.Count(); + if( ( 0 == totalNumberOfGlyphs ) || + ( 0 == totalNumberOfLines ) ) { return logicalIndex; } @@ -184,14 +275,43 @@ CharacterIndex GetClosestCursorIndex( VisualModelPtr visualModel, // Get the position of the first glyph. const Vector2& position = *( positionsBuffer + firstLogicalGlyphIndex ); - // Whether the glyph can be split, like Latin ligatures fi, ff or Arabic ﻻ. - const Length numberOfCharacters = *( charactersPerGlyphBuffer + firstLogicalGlyphIndex ); + // Whether the glyph can be split, like Latin ligatures fi, ff or Arabic (ل + ا). + Length numberOfCharacters = *( charactersPerGlyphBuffer + firstLogicalGlyphIndex ); if( direction != LTR ) { // As characters are being traversed in visual order, // for right to left ligatures, the character which contains the // number of glyphs in the table is found first. // Jump the number of characters to the next glyph is needed. + + if( 0u == numberOfCharacters ) + { + // TODO: This is a workaround to fix an issue with complex characters in the arabic + // script like i.e. رّ or الأَبْجَدِيَّة العَرَبِيَّة + // There are characters that are not shaped in one glyph but in combination with + // the next one generates two of them. + // The visual to logical conversion table have characters in different order than + // expected even if all of them are arabic. + + // The workaround doesn't fix the issue completely but it prevents the application + // to hang in an infinite loop. + + // Find the number of characters. + for( GlyphIndex index = firstLogicalGlyphIndex + 1u; + ( 0u == numberOfCharacters ) && ( index < totalNumberOfGlyphs ) ; + ++index ) + { + numberOfCharacters = *( charactersPerGlyphBuffer + index ); + } + + if( 2u > numberOfCharacters ) + { + continue; + } + + --numberOfCharacters; + } + visualIndex += numberOfCharacters - 1u; } @@ -264,7 +384,7 @@ CharacterIndex GetClosestCursorIndex( VisualModelPtr visualModel, { // The paragraph direction is right to left. - if( ( lineIndex != numberOfLines - 1u ) && // is not the last line. + if( ( lineIndex != totalNumberOfLines - 1u ) && // is not the last line. ( visualIndex == startCharacter ) ) { // It places the cursor just after the first character in visual order. @@ -285,7 +405,7 @@ CharacterIndex GetClosestCursorIndex( VisualModelPtr visualModel, // This branch checks if the closest line is the one with the last '\n'. If it is, it decrements the visual index to place // the cursor just before the last '\n'. - if( ( lineIndex != numberOfLines - 1u ) && + if( ( lineIndex != totalNumberOfLines - 1u ) && TextAbstraction::IsNewParagraph( *( logicalModel->mText.Begin() + visualIndex - 1u ) ) ) { --visualIndex; @@ -558,7 +678,7 @@ void GetCursorPosition( VisualModelPtr visualModel, ( 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 ); + cursorInfo.secondaryPosition.y = cursorInfo.lineOffset + cursorInfo.lineHeight - cursorInfo.secondaryCursorHeight; // Transform the cursor info from line's coords to text's coords. cursorInfo.secondaryPosition.x += line.alignmentOffset; @@ -566,7 +686,7 @@ void GetCursorPosition( VisualModelPtr visualModel, } } -void FindSelectionIndices( VisualModelPtr visualModel, +bool FindSelectionIndices( VisualModelPtr visualModel, LogicalModelPtr logicalModel, MetricsPtr metrics, float visualX, @@ -574,52 +694,122 @@ void FindSelectionIndices( VisualModelPtr visualModel, CharacterIndex& startIndex, CharacterIndex& endIndex ) { + +/* + Hit character Select +|-------------------------------------------------------|------------------------------------------| +| On a word | The word | +| On a single white space between words | The word before or after the white space | +| On one of the multiple contiguous white spaces | The white spaces | +| On a single white space which is in the position zero | The white space and the next word | +| On a new paragraph character | The word or group of white spaces before | +|-------------------------------------------------------|------------------------------------------| +*/ + 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 ) + const Length totalNumberOfCharacters = logicalModel->mText.Count(); + + DALI_ASSERT_DEBUG( ( hitCharacter <= totalNumberOfCharacters ) && "GetClosestCursorIndex returned out of bounds index" ); + + if( 0u == totalNumberOfCharacters ) { - return; // if model empty + // Nothing to do if the model is empty. + return false; } - if( hitCharacter >= logicalModel->mText.Count() ) + if( hitCharacter >= totalNumberOfCharacters ) { // Closest hit character is the last character. - if( hitCharacter == logicalModel->mText.Count() ) + if( hitCharacter == totalNumberOfCharacters ) { hitCharacter--; //Hit character index set to last character in logical model } else { // hitCharacter is out of bounds - return; + return false; } } + const Character* const textBuffer = logicalModel->mText.Begin(); + startIndex = hitCharacter; endIndex = hitCharacter; - bool isHitCharacterWhitespace = TextAbstraction::IsWhiteSpace( logicalModel->mText[hitCharacter] ); - // Find the start and end of the text - for( startIndex = hitCharacter; startIndex > 0; --startIndex ) + // Whether the hit character is a new paragraph character. + const bool isHitCharacterNewParagraph = TextAbstraction::IsNewParagraph( *( textBuffer + hitCharacter ) ); + + // Whether the hit character is a white space. Note a new paragraph character is a white space as well but here is not wanted. + const bool isHitCharacterWhiteSpace = TextAbstraction::IsWhiteSpace( *( textBuffer + hitCharacter ) ) && !isHitCharacterNewParagraph; + + FindWordData data( textBuffer, + totalNumberOfCharacters, + hitCharacter, + isHitCharacterWhiteSpace, + isHitCharacterNewParagraph ); + + if( isHitCharacterNewParagraph ) { - if( isHitCharacterWhitespace != TextAbstraction::IsWhiteSpace( logicalModel->mText[ startIndex-1 ] ) ) + // Find the first character before the hit one which is not a new paragraph character. + + if( hitCharacter > 0u ) { - break; + endIndex = hitCharacter - 1u; + for( ; endIndex > 0; --endIndex ) + { + const Dali::Toolkit::Text::Character character = *( data.textBuffer + endIndex ); + + if( !Dali::TextAbstraction::IsNewParagraph( character ) ) + { + break; + } + } } + + data.hitCharacter = endIndex; + data.isNewParagraph = false; + data.isWhiteSpace = TextAbstraction::IsWhiteSpace( *( textBuffer + data.hitCharacter ) ); } - const CharacterIndex pastTheEnd = logicalModel->mText.Count(); - for( endIndex = hitCharacter + 1u; endIndex < pastTheEnd; ++endIndex ) + + // Find the start of the word. + FindStartOfWord( data ); + startIndex = data.foundIndex; + + // Find the end of the word. + FindEndOfWord( data ); + endIndex = data.foundIndex; + + if( 1u == ( endIndex - startIndex ) ) { - if( isHitCharacterWhitespace != TextAbstraction::IsWhiteSpace( logicalModel->mText[ endIndex ] ) ) + if( isHitCharacterWhiteSpace ) { - break; + // Select the word before or after the white space + + if( 0u == hitCharacter ) + { + data.isWhiteSpace = false; + FindEndOfWord( data ); + endIndex = data.foundIndex; + } + else if( hitCharacter > 0u ) + { + // Find the start of the word. + data.hitCharacter = hitCharacter - 1u; + data.isWhiteSpace = false; + FindStartOfWord( data ); + startIndex = data.foundIndex; + + --endIndex; + } } } + + return true; } } // namespace Text