X-Git-Url: http://review.tizen.org/git/?p=platform%2Fcore%2Fuifw%2Fdali-toolkit.git;a=blobdiff_plain;f=dali-toolkit%2Finternal%2Ftext%2Ftext-controller-impl.cpp;h=26fb42254684f0a2ede8ffcc09e9b52a73d246d8;hp=5c93ffe6860e3dd02d7128e16d0fb14e8aa56c69;hb=e4f601ea17cf4b2bb7da1393a62520ec147437c5;hpb=da7bba2130b0b15f9d93d63b2404b8888af2c85b diff --git a/dali-toolkit/internal/text/text-controller-impl.cpp b/dali-toolkit/internal/text/text-controller-impl.cpp index 5c93ffe..26fb422 100644 --- a/dali-toolkit/internal/text/text-controller-impl.cpp +++ b/dali-toolkit/internal/text/text-controller-impl.cpp @@ -25,13 +25,9 @@ // INTERNAL INCLUDES #include #include -#include #include -#include #include #include -#include -#include namespace { @@ -81,8 +77,8 @@ namespace Text * @param[in] glyphIndex The index to the first glyph. * @param[in] numberOfGlyphs The number of glyphs. * @param[out] glyphMetrics Some glyph metrics (font height, advance, ascender and x bearing). - * @param[in] - * @param[in] + * @param[in] visualModel The visual model. + * @param[in] fontClient The font client. */ void GetGlyphsMetrics( GlyphIndex glyphIndex, Length numberOfGlyphs, @@ -329,7 +325,6 @@ void Controller::Impl::UpdateModel( OperationsMask operationsRequired ) { // Retrieves the scripts used in the text. multilanguageSupport.SetScripts( utf32Characters, - lineBreakInfo, scripts ); } @@ -352,13 +347,12 @@ void Controller::Impl::UpdateModel( OperationsMask operationsRequired ) Vector mirroredUtf32Characters; bool textMirrored = false; + Length numberOfParagraphs = 0u; if( BIDI_INFO & operations ) { // Count the number of LINE_NO_BREAK to reserve some space for the vector of paragraph's // bidirectional info. - Length numberOfParagraphs = 0u; - const TextAbstraction::LineBreakInfo* lineBreakInfoBuffer = lineBreakInfo.Begin(); for( Length index = 0u; index < numberOfCharacters; ++index ) { @@ -382,7 +376,9 @@ void Controller::Impl::UpdateModel( OperationsMask operationsRequired ) // This paragraph has right to left text. Some characters may need to be mirrored. // TODO: consider if the mirrored string can be stored as well. - textMirrored = GetMirroredText( utf32Characters, mirroredUtf32Characters ); + textMirrored = GetMirroredText( utf32Characters, + mirroredUtf32Characters, + bidirectionalInfo ); // Only set the character directions if there is right to left characters. Vector& directions = mLogicalModel->mCharacterDirections; @@ -396,12 +392,14 @@ void Controller::Impl::UpdateModel( OperationsMask operationsRequired ) // There is no right to left characters. Clear the directions vector. mLogicalModel->mCharacterDirections.Clear(); } - - } + } Vector& glyphs = mVisualModel->mGlyphs; Vector& glyphsToCharactersMap = mVisualModel->mGlyphsToCharacters; Vector& charactersPerGlyph = mVisualModel->mCharactersPerGlyph; + Vector newParagraphGlyphs; + newParagraphGlyphs.Reserve( numberOfParagraphs ); + if( SHAPE_TEXT & operations ) { const Vector& textToShape = textMirrored ? mirroredUtf32Characters : utf32Characters; @@ -412,7 +410,8 @@ void Controller::Impl::UpdateModel( OperationsMask operationsRequired ) validFonts, glyphs, glyphsToCharactersMap, - charactersPerGlyph ); + charactersPerGlyph, + newParagraphGlyphs ); // Create the 'number of glyphs' per character and the glyph to character conversion tables. mVisualModel->CreateGlyphsPerCharacterTable( numberOfCharacters ); @@ -423,7 +422,19 @@ void Controller::Impl::UpdateModel( OperationsMask operationsRequired ) if( GET_GLYPH_METRICS & operations ) { - mFontClient.GetGlyphMetrics( glyphs.Begin(), numberOfGlyphs ); + GlyphInfo* glyphsBuffer = glyphs.Begin(); + mFontClient.GetGlyphMetrics( glyphsBuffer, numberOfGlyphs ); + + // Update the width and advance of all new paragraph characters. + for( Vector::ConstIterator it = newParagraphGlyphs.Begin(), endIt = newParagraphGlyphs.End(); it != endIt; ++it ) + { + const GlyphIndex index = *it; + GlyphInfo& glyph = *( glyphsBuffer + index ); + + glyph.xBearing = 0.f; + glyph.width = 0.f; + glyph.advance = 0.f; + } } } @@ -663,7 +674,7 @@ void Controller::Impl::OnHandleEvent( const Event& event ) if( handleStopScrolling ) { - mEventData->mUpdateLeftSelectionPosition = ( mEventData->mLeftSelectionPosition != handlePosition ) && ( mEventData->mRightSelectionPosition != handlePosition); + mEventData->mUpdateLeftSelectionPosition = ( mEventData->mRightSelectionPosition != handlePosition ); mEventData->mScrollAfterUpdatePosition = mEventData->mUpdateLeftSelectionPosition; if( mEventData->mUpdateLeftSelectionPosition ) @@ -681,9 +692,8 @@ void Controller::Impl::OnHandleEvent( const Event& event ) if( handleStopScrolling ) { - mEventData->mUpdateRightSelectionPosition = ( mEventData->mRightSelectionPosition != handlePosition ) && ( mEventData->mLeftSelectionPosition != handlePosition ); + mEventData->mUpdateRightSelectionPosition = ( mEventData->mLeftSelectionPosition != handlePosition ); mEventData->mScrollAfterUpdatePosition = mEventData->mUpdateRightSelectionPosition; - if( mEventData->mUpdateRightSelectionPosition ) { mEventData->mRightSelectionPosition = handlePosition; @@ -705,74 +715,84 @@ void Controller::Impl::OnHandleEvent( const Event& event ) ClampHorizontalScroll( actualSize ); + bool endOfScroll = false; if( Vector2::ZERO == ( currentScrollPosition - mEventData->mScrollPosition ) ) { // Notify the decorator there is no more text to scroll. // The decorator won't send more scroll events. mEventData->mDecorator->NotifyEndOfScroll(); + // Still need to set the position of the handle. + endOfScroll = true; } - else - { - const bool scrollRightDirection = xSpeed > 0.f; - const bool leftSelectionHandleEvent = Event::LEFT_SELECTION_HANDLE_EVENT == event.type; - const bool rightSelectionHandleEvent = Event::RIGHT_SELECTION_HANDLE_EVENT == event.type; - if( Event::GRAB_HANDLE_EVENT == event.type ) - { - ChangeState( EventData::GRAB_HANDLE_PANNING ); + // Set the position of the handle. + const bool scrollRightDirection = xSpeed > 0.f; + const bool leftSelectionHandleEvent = Event::LEFT_SELECTION_HANDLE_EVENT == event.type; + const bool rightSelectionHandleEvent = Event::RIGHT_SELECTION_HANDLE_EVENT == event.type; - Vector2 position = mEventData->mDecorator->GetPosition( GRAB_HANDLE ); + if( Event::GRAB_HANDLE_EVENT == event.type ) + { + ChangeState( EventData::GRAB_HANDLE_PANNING ); - // Position the grag handle close to either the left or right edge. - position.x = scrollRightDirection ? 0.f : mControlSize.width; + Vector2 position = mEventData->mDecorator->GetPosition( GRAB_HANDLE ); - // 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 ); + // Position the grag handle close to either the left or right edge. + position.x = scrollRightDirection ? 0.f : mVisualModel->mControlSize.width; - mEventData->mUpdateCursorPosition = mEventData->mPrimaryCursorPosition != handlePosition; - mEventData->mScrollAfterUpdatePosition = mEventData->mUpdateCursorPosition; - mEventData->mPrimaryCursorPosition = handlePosition; - } - else if( leftSelectionHandleEvent || rightSelectionHandleEvent ) - { - // TODO: This is recalculating the selection box every time the text is scrolled with the selection handles. - // Think if something can be done to save power. + // 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 ); - ChangeState( EventData::SELECTION_HANDLE_PANNING ); + mEventData->mUpdateCursorPosition = mEventData->mPrimaryCursorPosition != handlePosition; + mEventData->mScrollAfterUpdatePosition = mEventData->mUpdateCursorPosition; + mEventData->mPrimaryCursorPosition = handlePosition; + } + else if( leftSelectionHandleEvent || rightSelectionHandleEvent ) + { + // TODO: This is recalculating the selection box every time the text is scrolled with the selection handles. + // Think if something can be done to save power. + + ChangeState( EventData::SELECTION_HANDLE_PANNING ); - Vector2 position = mEventData->mDecorator->GetPosition( leftSelectionHandleEvent ? Text::LEFT_SELECTION_HANDLE : Text::RIGHT_SELECTION_HANDLE ); + Vector2 position = mEventData->mDecorator->GetPosition( leftSelectionHandleEvent ? Text::LEFT_SELECTION_HANDLE : Text::RIGHT_SELECTION_HANDLE ); - // Position the selection handle close to either the left or right edge. - position.x = scrollRightDirection ? 0.f : mControlSize.width; + // Position the selection handle close to either the left or right edge. + position.x = scrollRightDirection ? 0.f : mVisualModel->mControlSize.width; - // 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 ); + // 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 ); - if( leftSelectionHandleEvent ) + if( leftSelectionHandleEvent ) + { + const bool differentHandles = ( mEventData->mLeftSelectionPosition != handlePosition ) && ( mEventData->mRightSelectionPosition != handlePosition ); + mEventData->mUpdateLeftSelectionPosition = endOfScroll || differentHandles; + if( differentHandles ) { - mEventData->mUpdateLeftSelectionPosition = handlePosition != mEventData->mLeftSelectionPosition; mEventData->mLeftSelectionPosition = handlePosition; } - else + } + else + { + const bool differentHandles = ( mEventData->mRightSelectionPosition != handlePosition ) && ( mEventData->mLeftSelectionPosition != handlePosition ); + mEventData->mUpdateRightSelectionPosition = endOfScroll || differentHandles; + if( differentHandles ) { - mEventData->mUpdateRightSelectionPosition = handlePosition != mEventData->mRightSelectionPosition; mEventData->mRightSelectionPosition = handlePosition; } + } - if( mEventData->mUpdateLeftSelectionPosition || mEventData->mUpdateRightSelectionPosition ) - { - RepositionSelectionHandles( mEventData->mLeftSelectionPosition, - mEventData->mRightSelectionPosition ); + if( mEventData->mUpdateLeftSelectionPosition || mEventData->mUpdateRightSelectionPosition ) + { + RepositionSelectionHandles( mEventData->mLeftSelectionPosition, + mEventData->mRightSelectionPosition ); - mEventData->mScrollAfterUpdatePosition = true; - } + mEventData->mScrollAfterUpdatePosition = true; } - mEventData->mDecoratorUpdated = true; } + mEventData->mDecoratorUpdated = true; } // end ( HANDLE_SCROLLING == state ) } @@ -914,34 +934,108 @@ void Controller::Impl::RepositionSelectionHandles( CharacterIndex selectionStart const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin(); const GlyphInfo* const glyphsBuffer = mVisualModel->mGlyphs.Begin(); const Vector2* const positionsBuffer = mVisualModel->mGlyphPositions.Begin(); + const Length* const charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin(); + const CharacterIndex* const glyphToCharacterBuffer = mVisualModel->mGlyphsToCharacters.Begin(); + const CharacterDirection* const modelCharacterDirectionsBuffer = ( 0u != mLogicalModel->mCharacterDirections.Count() ) ? mLogicalModel->mCharacterDirections.Begin() : NULL; // TODO: Better algorithm to create the highlight box. // TODO: Multi-line. + // Get the height of the line. const Vector& lines = mVisualModel->mLines; const LineRun& firstLine = *lines.Begin(); const float height = firstLine.ascender + -firstLine.descender; + // Swap the indices if the start is greater than the end. const bool indicesSwapped = ( selectionStart > selectionEnd ); if( indicesSwapped ) { std::swap( selectionStart, selectionEnd ); } - GlyphIndex glyphStart = *( charactersToGlyphBuffer + selectionStart ); - GlyphIndex glyphEnd = *( charactersToGlyphBuffer + ( selectionEnd - 1u ) ) + *( glyphsPerCharacterBuffer + ( selectionEnd - 1u ) ) - 1u; + // Get the indices to the first and last selected glyphs. + const CharacterIndex selectionEndMinusOne = selectionEnd - 1u; + const GlyphIndex glyphStart = *( charactersToGlyphBuffer + selectionStart ); + const Length numberOfGlyphs = *( glyphsPerCharacterBuffer + selectionEndMinusOne ); + const GlyphIndex glyphEnd = *( charactersToGlyphBuffer + selectionEndMinusOne ) + ( ( numberOfGlyphs > 0 ) ? numberOfGlyphs - 1u : 0u ); + + // Check if the first glyph is a ligature that must be broken like Latin ff, fi, or Arabic ï»», etc which needs special code. + const Length numberOfCharactersStart = *( charactersPerGlyphBuffer + glyphStart ); + bool splitStartGlyph = ( numberOfCharactersStart > 1u ) && HasLigatureMustBreak( mLogicalModel->GetScript( selectionStart ) ); + + // Check if the last glyph is a ligature that must be broken like Latin ff, fi, or Arabic ï»», etc which needs special code. + const Length numberOfCharactersEnd = *( charactersPerGlyphBuffer + glyphEnd ); + bool splitEndGlyph = ( glyphStart != glyphEnd ) && ( numberOfCharactersEnd > 1u ) && HasLigatureMustBreak( mLogicalModel->GetScript( selectionEndMinusOne ) ); + // Tell the decorator to swap the selection handles if needed. mEventData->mDecorator->SwapSelectionHandlesEnabled( firstLine.direction != indicesSwapped ); const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset; + // Traverse the glyphs. for( GlyphIndex index = glyphStart; index <= glyphEnd; ++index ) { const GlyphInfo& glyph = *( glyphsBuffer + index ); const Vector2& position = *( positionsBuffer + index ); + if( splitStartGlyph ) + { + // If the first glyph is a ligature that must be broken it may be needed to add only part of the glyph to the highlight box. + + const float glyphAdvance = glyph.advance / static_cast( numberOfCharactersStart ); + const CharacterIndex interGlyphIndex = selectionStart - *( glyphToCharacterBuffer + glyphStart ); + // Get the direction of the character. + CharacterDirection isCurrentRightToLeft = false; + if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right. + { + isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + selectionStart ); + } + + // The end point could be in the middle of the ligature. + // Calculate the number of characters selected. + const Length numberOfCharacters = ( glyphStart == glyphEnd ) ? ( selectionEnd - selectionStart ) : ( numberOfCharactersStart - interGlyphIndex ); + + const float xPosition = position.x - glyph.xBearing + offset.x + glyphAdvance * static_cast( isCurrentRightToLeft ? ( numberOfCharactersStart - interGlyphIndex - numberOfCharacters ) : interGlyphIndex ); + + mEventData->mDecorator->AddHighlight( xPosition, + offset.y, + xPosition + static_cast( numberOfCharacters ) * glyphAdvance, + offset.y + height ); + + splitStartGlyph = false; + continue; + } + + if( splitEndGlyph && ( index == glyphEnd ) ) + { + // Equally, if the last glyph is a ligature that must be broken it may be needed to add only part of the glyph to the highlight box. + + const float glyphAdvance = glyph.advance / static_cast( numberOfCharactersEnd ); + const CharacterIndex interGlyphIndex = selectionEnd - *( glyphToCharacterBuffer + glyphEnd ); + // Get the direction of the character. + CharacterDirection isCurrentRightToLeft = false; + if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right. + { + isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + selectionEnd ); + } + + const Length numberOfCharacters = numberOfCharactersEnd - interGlyphIndex; + + const float xPosition = position.x - glyph.xBearing + offset.x + ( isCurrentRightToLeft ? ( glyphAdvance * static_cast( numberOfCharacters ) ) : 0.f ); + mEventData->mDecorator->AddHighlight( xPosition, + offset.y, + xPosition + static_cast( interGlyphIndex ) * glyphAdvance, + offset.y + height ); + + splitEndGlyph = false; + continue; + } + const float xPosition = position.x - glyph.xBearing + offset.x; - mEventData->mDecorator->AddHighlight( xPosition, offset.y, xPosition + glyph.advance, height ); + mEventData->mDecorator->AddHighlight( xPosition, + offset.y, + xPosition + glyph.advance, + offset.y + height ); } CursorInfo primaryCursorInfo; @@ -959,6 +1053,9 @@ void Controller::Impl::RepositionSelectionHandles( CharacterIndex selectionStart mEventData->mDecorator->SetPosition( RIGHT_SELECTION_HANDLE, secondaryPosition.x, secondaryPosition.y, secondaryCursorInfo.lineHeight ); + // Cursor to be positioned at end of selection so if selection interrupted and edit mode restarted the cursor will be at end of selection + mEventData->mPrimaryCursorPosition = (indicesSwapped)?mEventData->mLeftSelectionPosition:mEventData->mRightSelectionPosition; + // Set the flag to update the decorator. mEventData->mDecoratorUpdated = true; } @@ -1223,10 +1320,25 @@ LineIndex Controller::Impl::GetClosestLine( float y ) const 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() ) { - // Selection out of bounds. - return; + // 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; @@ -1258,6 +1370,8 @@ void Controller::Impl::FindSelectionIndices( float visualX, float visualY, Chara 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. @@ -1328,8 +1442,8 @@ CharacterIndex Controller::Impl::GetClosestCursorIndex( float visualX, const Vector2& position = *( positionsBuffer + glyphLogicalOrderIndex ); - // Prevents to jump the whole Latin ligatures like fi, ff, ... - const Length numberOfCharactersInLigature = ( TextAbstraction::LATIN == script ) ? *( charactersPerGlyphBuffer + glyphLogicalOrderIndex ) : 1u; + // Prevents to jump the whole Latin ligatures like fi, ff, or Arabic ï»»... + const Length numberOfCharactersInLigature = HasLigatureMustBreak( script ) ? *( charactersPerGlyphBuffer + glyphLogicalOrderIndex ) : 1u; const float glyphAdvance = glyphMetrics.advance / static_cast( numberOfCharactersInLigature ); for( GlyphIndex index = 0u; !matched && ( index < numberOfCharactersInLigature ); ++index ) @@ -1361,6 +1475,8 @@ CharacterIndex Controller::Impl::GetClosestCursorIndex( float visualX, logicalIndex = hasRightToLeftCharacters ? *( visualToLogicalCursorBuffer + 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; } @@ -1379,37 +1495,33 @@ void Controller::Impl::GetCursorPosition( CharacterIndex logical, cursorInfo.lineHeight = GetDefaultFontLineHeight(); cursorInfo.primaryCursorHeight = cursorInfo.lineHeight; - cursorInfo.primaryPosition.x = 1.f; + cursorInfo.primaryPosition.x = mEventData->mDecorator->GetCursorWidth(); cursorInfo.primaryPosition.y = 0.f; // Nothing else to do. return; } - // Get the previous logical index. - const CharacterIndex previousLogical = isFirstPosition ? 0u : logical - 1u; + // '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; - // Decrease the logical index if it's the last one. - if( isLastPosition ) - { - --logical; - } - - // Get the direction of the character and the previous one. + // 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 isPreviousRightToLeft = false; + CharacterDirection isNextRightToLeft = false; if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right. { - isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + logical ); - isPreviousRightToLeft = *( modelCharacterDirectionsBuffer + previousLogical ); + isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + characterIndex ); + isNextRightToLeft = *( modelCharacterDirectionsBuffer + nextCharacterIndex ); } // Get the line where the character is laid-out. - const LineRun* modelLines = mVisualModel->mLines.Begin(); + const LineRun* const modelLines = mVisualModel->mLines.Begin(); - const LineIndex lineIndex = mVisualModel->GetLineOfCharacter( logical ); + const LineIndex lineIndex = mVisualModel->GetLineOfCharacter( characterIndex ); const LineRun& line = *( modelLines + lineIndex ); // Get the paragraph's direction. @@ -1417,123 +1529,149 @@ void Controller::Impl::GetCursorPosition( CharacterIndex logical, // Check whether there is an alternative position: - cursorInfo.isSecondaryCursor = ( isCurrentRightToLeft != isPreviousRightToLeft ) || - ( isLastPosition && ( isRightToLeftParagraph != isCurrentRightToLeft ) ); + cursorInfo.isSecondaryCursor = ( !isLastPosition && ( isCurrentRightToLeft != isNextRightToLeft ) ) || + ( isLastPosition && ( isRightToLeftParagraph != isCurrentRightToLeft ) ); // Set the line height. cursorInfo.lineHeight = line.ascender + -line.descender; - // Convert the cursor position into the glyph position. - CharacterIndex characterIndex = logical; - if( cursorInfo.isSecondaryCursor && - ( isRightToLeftParagraph != isCurrentRightToLeft ) ) + // Calculate the primary cursor. + + CharacterIndex index = characterIndex; + if( cursorInfo.isSecondaryCursor ) { - characterIndex = previousLogical; + // 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 currentGlyphIndex = *( mVisualModel->mCharactersToGlyph.Begin() + characterIndex ); - const Length numberOfGlyphs = *( mVisualModel->mGlyphsPerCharacter.Begin() + characterIndex ); - const Length numberOfCharacters = *( mVisualModel->mCharactersPerGlyph.Begin() +currentGlyphIndex ); + 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(); + + // 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( currentGlyphIndex, - numberOfGlyphs, + GetGlyphsMetrics( primaryGlyphIndex, + primaryNumberOfGlyphs, glyphMetrics, mVisualModel, mFontClient ); - float interGlyphAdvance = 0.f; + // 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 && - ( numberOfCharacters > 1u ) ) + ( primaryNumberOfCharacters > 1u ) ) { - const CharacterIndex firstIndex = *( mVisualModel->mGlyphsToCharacters.Begin() + currentGlyphIndex ); - interGlyphAdvance = static_cast( characterIndex - firstIndex ) * glyphMetrics.advance / static_cast( numberOfCharacters ); - } - - // Get the glyph position and x bearing. - const Vector2& currentPosition = *( mVisualModel->mGlyphPositions.Begin() + currentGlyphIndex ); - - // Set the cursor's height. - cursorInfo.primaryCursorHeight = glyphMetrics.fontHeight; + const CharacterIndex firstIndex = *( glyphsToCharactersBuffer + primaryGlyphIndex ); - // Set the position. - cursorInfo.primaryPosition.x = -glyphMetrics.xBearing + currentPosition.x + ( isCurrentRightToLeft ? glyphMetrics.advance : interGlyphAdvance ); - cursorInfo.primaryPosition.y = line.ascender - glyphMetrics.ascender; - - if( isLastPosition ) - { - // The position of the cursor after the last character needs special - // care depending on its direction and the direction of the paragraph. + bool isCurrentRightToLeft = false; + if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right. + { + isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + index ); + } - if( cursorInfo.isSecondaryCursor ) + Length numberOfGlyphAdvance = ( isFirstPosition ? 0u : 1u ) + characterIndex - firstIndex; + if( isCurrentRightToLeft ) { - // 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. + numberOfGlyphAdvance = primaryNumberOfCharacters - numberOfGlyphAdvance; + } - // TODO: check for more than one line! - characterIndex = isRightToLeftParagraph ? line.characterRun.characterIndex : line.characterRun.characterIndex + line.characterRun.numberOfCharacters - 1u; - characterIndex = mLogicalModel->GetLogicalCharacterIndex( characterIndex ); + glyphAdvance = static_cast( numberOfGlyphAdvance ) * glyphMetrics.advance / static_cast( primaryNumberOfCharacters ); + } - const GlyphIndex glyphIndex = *( mVisualModel->mCharactersToGlyph.Begin() + characterIndex ); - const Length numberOfGlyphs = *( mVisualModel->mGlyphsPerCharacter.Begin() + characterIndex ); + // Get the glyph position and x bearing. + const Vector2& primaryPosition = *( glyphPositionsBuffer + primaryGlyphIndex ); - const Vector2& position = *( mVisualModel->mGlyphPositions.Begin() + glyphIndex ); + // Set the primary cursor's height. + cursorInfo.primaryCursorHeight = cursorInfo.isSecondaryCursor ? 0.5f * glyphMetrics.fontHeight : glyphMetrics.fontHeight; - // Get the metrics for the group of glyphs. - GlyphMetrics glyphMetrics; - GetGlyphsMetrics( glyphIndex, - numberOfGlyphs, - glyphMetrics, - mVisualModel, - mFontClient ); + // Set the primary cursor's position. + cursorInfo.primaryPosition.x = -glyphMetrics.xBearing + primaryPosition.x + glyphAdvance; + cursorInfo.primaryPosition.y = line.ascender - glyphMetrics.ascender; - cursorInfo.primaryPosition.x = -glyphMetrics.xBearing + position.x + ( isRightToLeftParagraph ? 0.f : glyphMetrics.advance ); + // Calculate the secondary cursor. - cursorInfo.primaryPosition.y = line.ascender - glyphMetrics.ascender; - } - else + if( cursorInfo.isSecondaryCursor ) + { + // Set the secondary cursor's height. + cursorInfo.secondaryCursorHeight = 0.5f * glyphMetrics.fontHeight; + + CharacterIndex index = characterIndex; + if( !isLastPosition ) { - if( !isCurrentRightToLeft ) - { - cursorInfo.primaryPosition.x += glyphMetrics.advance; - } - else - { - cursorInfo.primaryPosition.x -= glyphMetrics.advance; - } + index = ( isRightToLeftParagraph == isCurrentRightToLeft ) ? nextCharacterIndex : characterIndex; } - } - // Set the alternative cursor position. - if( cursorInfo.isSecondaryCursor ) - { - // Convert the cursor position into the glyph position. - const CharacterIndex previousCharacterIndex = ( ( isRightToLeftParagraph != isCurrentRightToLeft ) ? logical : previousLogical ); - const GlyphIndex previousGlyphIndex = *( mVisualModel->mCharactersToGlyph.Begin() + previousCharacterIndex ); - const Length numberOfGlyphs = *( mVisualModel->mGlyphsPerCharacter.Begin() + previousCharacterIndex ); + const GlyphIndex secondaryGlyphIndex = *( charactersToGlyphBuffer + index ); + const Length secondaryNumberOfGlyphs = *( glyphsPerCharacterBuffer + index ); - // Get the glyph position. - const Vector2& previousPosition = *( mVisualModel->mGlyphPositions.Begin() + previousGlyphIndex ); + const Vector2& secondaryPosition = *( glyphPositionsBuffer + secondaryGlyphIndex ); - // Get the metrics for the group of glyphs. - GlyphMetrics glyphMetrics; - GetGlyphsMetrics( previousGlyphIndex, - numberOfGlyphs, + GetGlyphsMetrics( secondaryGlyphIndex, + secondaryNumberOfGlyphs, glyphMetrics, mVisualModel, mFontClient ); - // Set the cursor position and height. - cursorInfo.secondaryPosition.x = -glyphMetrics.xBearing + previousPosition.x + ( ( ( isLastPosition && !isCurrentRightToLeft ) || - ( !isLastPosition && isCurrentRightToLeft ) ) ? glyphMetrics.advance : 0.f ); - - cursorInfo.secondaryCursorHeight = 0.5f * glyphMetrics.fontHeight; - + // 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 ); - - // Update the primary cursor height as well. - cursorInfo.primaryCursorHeight *= 0.5f; } } @@ -1547,25 +1685,27 @@ CharacterIndex Controller::Impl::CalculateNewCursorIndex( CharacterIndex index ) CharacterIndex cursorIndex = mEventData->mPrimaryCursorPosition; - const Script script = mLogicalModel->GetScript( index ); - const GlyphIndex* charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin(); - const Length* charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin(); + const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin(); + const Length* const charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin(); + + GlyphIndex glyphIndex = *( charactersToGlyphBuffer + index ); + Length numberOfCharacters = *( charactersPerGlyphBuffer + glyphIndex ); - Length numberOfCharacters = 0u; - if( TextAbstraction::LATIN == script ) + if( numberOfCharacters > 1u ) { - // Prevents to jump the whole Latin ligatures like fi, ff, ... - numberOfCharacters = 1u; + const Script script = mLogicalModel->GetScript( index ); + if( HasLigatureMustBreak( script ) ) + { + // Prevents to jump the whole Latin ligatures like fi, ff, or Arabic ï»», ... + numberOfCharacters = 1u; + } } else { - GlyphIndex glyphIndex = *( charactersToGlyphBuffer + index ); - numberOfCharacters = *( charactersPerGlyphBuffer + glyphIndex ); - while( 0u == numberOfCharacters ) { - numberOfCharacters = *( charactersPerGlyphBuffer + glyphIndex ); ++glyphIndex; + numberOfCharacters = *( charactersPerGlyphBuffer + glyphIndex ); } } @@ -1591,7 +1731,7 @@ void Controller::Impl::UpdateCursorPosition() return; } - if( IsShowingPlaceholderText() ) + if( IsShowingPlaceholderText() || ( 0u == mLogicalModel->mText.Count() ) ) { // Do not want to use the place-holder text to set the cursor position. @@ -1624,17 +1764,17 @@ void Controller::Impl::UpdateCursorPosition() { case LayoutEngine::HORIZONTAL_ALIGN_BEGIN: { - cursorPosition.x = 1.f; + cursorPosition.x = mEventData->mDecorator->GetCursorWidth(); break; } case LayoutEngine::HORIZONTAL_ALIGN_CENTER: { - cursorPosition.x = floor( 0.5f * mControlSize.width ); + cursorPosition.x = floor( 0.5f * mVisualModel->mControlSize.width ); break; } case LayoutEngine::HORIZONTAL_ALIGN_END: { - cursorPosition.x = mControlSize.width; + cursorPosition.x = mVisualModel->mControlSize.width; break; } } @@ -1648,12 +1788,12 @@ void Controller::Impl::UpdateCursorPosition() } case LayoutEngine::VERTICAL_ALIGN_CENTER: { - cursorPosition.y = floorf( 0.5f * ( mControlSize.height - lineHeight ) ); + cursorPosition.y = floorf( 0.5f * ( mVisualModel->mControlSize.height - lineHeight ) ); break; } case LayoutEngine::VERTICAL_ALIGN_BOTTOM: { - cursorPosition.y = mControlSize.height - lineHeight; + cursorPosition.y = mVisualModel->mControlSize.height - lineHeight; break; } } @@ -1738,9 +1878,9 @@ void Controller::Impl::UpdateSelectionHandle( HandleType handleType ) void Controller::Impl::ClampHorizontalScroll( const Vector2& actualSize ) { // Clamp between -space & 0 (and the text alignment). - if( actualSize.width > mControlSize.width ) + if( actualSize.width > mVisualModel->mControlSize.width ) { - const float space = ( actualSize.width - mControlSize.width ) + mAlignmentOffset.x; + const float space = ( actualSize.width - mVisualModel->mControlSize.width ) + mAlignmentOffset.x; mEventData->mScrollPosition.x = ( mEventData->mScrollPosition.x < -space ) ? -space : mEventData->mScrollPosition.x; mEventData->mScrollPosition.x = ( mEventData->mScrollPosition.x > -mAlignmentOffset.x ) ? -mAlignmentOffset.x : mEventData->mScrollPosition.x; @@ -1755,9 +1895,9 @@ void Controller::Impl::ClampHorizontalScroll( const Vector2& actualSize ) void Controller::Impl::ClampVerticalScroll( const Vector2& actualSize ) { // Clamp between -space & 0 (and the text alignment). - if( actualSize.height > mControlSize.height ) + if( actualSize.height > mVisualModel->mControlSize.height ) { - const float space = ( actualSize.height - mControlSize.height ) + mAlignmentOffset.y; + const float space = ( actualSize.height - mVisualModel->mControlSize.height ) + mAlignmentOffset.y; mEventData->mScrollPosition.y = ( mEventData->mScrollPosition.y < -space ) ? -space : mEventData->mScrollPosition.y; mEventData->mScrollPosition.y = ( mEventData->mScrollPosition.y > -mAlignmentOffset.y ) ? -mAlignmentOffset.y : mEventData->mScrollPosition.y; @@ -1779,9 +1919,9 @@ void Controller::Impl::ScrollToMakePositionVisible( const Vector2& position ) mEventData->mScrollPosition.x += offset.x; updateDecorator = true; } - else if( position.x > mControlSize.width ) + else if( position.x > mVisualModel->mControlSize.width ) { - offset.x = mControlSize.width - position.x; + offset.x = mVisualModel->mControlSize.width - position.x; mEventData->mScrollPosition.x += offset.x; updateDecorator = true; } @@ -1836,8 +1976,9 @@ void Controller::Impl::ScrollTextToMatchCursor() } // Set which cursors are active according the state. - if( ( EventData::EDITING == mEventData->mState ) || - ( EventData::EDITING_WITH_POPUP == mEventData->mState ) || + if( ( EventData::EDITING == mEventData->mState ) || + ( EventData::EDITING_WITH_POPUP == mEventData->mState ) || + ( EventData::EDITING_WITH_GRAB_HANDLE == mEventData->mState ) || ( EventData::GRAB_HANDLE_PANNING == mEventData->mState ) ) { if( cursorInfo.isSecondaryCursor )