2 * Copyright (c) 2015 Samsung Electronics Co., Ltd.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
19 #include <dali-toolkit/internal/text/text-controller-impl.h>
22 #include <dali/public-api/adaptor-framework/key.h>
25 #include <dali-toolkit/internal/text/bidirectional-support.h>
26 #include <dali-toolkit/internal/text/character-set-conversion.h>
27 #include <dali-toolkit/internal/text/layouts/layout-parameters.h>
28 #include <dali-toolkit/internal/text/multi-language-support.h>
29 #include <dali-toolkit/internal/text/script-run.h>
30 #include <dali-toolkit/internal/text/segmentation.h>
31 #include <dali-toolkit/internal/text/shaper.h>
32 #include <dali-toolkit/internal/text/text-io.h>
33 #include <dali-toolkit/internal/text/text-view.h>
39 * @brief Some characters can be shaped in more than one glyph.
40 * This struct is used to retrieve metrics from these group of glyphs.
54 float fontHeight; ///< The font's height of that glyphs.
55 float advance; ///< The sum of all the advances of all the glyphs.
56 float ascender; ///< The font's ascender.
57 float xBearing; ///< The x bearing of the first glyph.
60 const std::string EMPTY_STRING("");
74 * @brief Get some glyph's metrics of a group of glyphs formed as a result of shaping one character.
76 * @param[in] glyphIndex The index to the first glyph.
77 * @param[in] numberOfGlyphs The number of glyphs.
78 * @param[out] glyphMetrics Some glyph metrics (font height, advance, ascender and x bearing).
82 void GetGlyphsMetrics( GlyphIndex glyphIndex,
83 Length numberOfGlyphs,
84 GlyphMetrics& glyphMetrics,
85 VisualModelPtr visualModel,
86 TextAbstraction::FontClient& fontClient )
88 const GlyphInfo* glyphsBuffer = visualModel->mGlyphs.Begin();
90 const GlyphInfo& firstGlyph = *( glyphsBuffer + glyphIndex );
92 Text::FontMetrics fontMetrics;
93 fontClient.GetFontMetrics( firstGlyph.fontId, fontMetrics );
95 glyphMetrics.fontHeight = fontMetrics.height;
96 glyphMetrics.advance = firstGlyph.advance;
97 glyphMetrics.ascender = fontMetrics.ascender;
98 glyphMetrics.xBearing = firstGlyph.xBearing;
100 for( unsigned int i = 1u; i < numberOfGlyphs; ++i )
102 const GlyphInfo& glyphInfo = *( glyphsBuffer + glyphIndex + i );
104 glyphMetrics.advance += glyphInfo.advance;
108 EventData::EventData( DecoratorPtr decorator )
109 : mDecorator( decorator ),
110 mPlaceholderTextActive(),
111 mPlaceholderTextInactive(),
112 mPlaceholderTextColor( 0.8f, 0.8f, 0.8f, 0.8f ),
116 mPrimaryCursorPosition( 0u ),
117 mLeftSelectionPosition( 0u ),
118 mRightSelectionPosition( 0u ),
119 mPreEditStartPosition( 0u ),
120 mPreEditLength( 0u ),
121 mIsShowingPlaceholderText( false ),
122 mPreEditFlag( false ),
123 mDecoratorUpdated( false ),
124 mCursorBlinkEnabled( true ),
125 mGrabHandleEnabled( true ),
126 mGrabHandlePopupEnabled( false ),
127 mSelectionEnabled( false ),
128 mHorizontalScrollingEnabled( true ),
129 mVerticalScrollingEnabled( false ),
130 mUpdateCursorPosition( false ),
131 mUpdateLeftSelectionPosition( false ),
132 mUpdateRightSelectionPosition( false ),
133 mScrollAfterUpdateCursorPosition( false )
136 EventData::~EventData()
139 bool Controller::Impl::ProcessInputEvents()
141 if( NULL == mEventData )
143 // Nothing to do if there is no text input.
147 mEventData->mDecoratorUpdated = false;
149 if( mEventData->mDecorator )
151 for( std::vector<Event>::iterator iter = mEventData->mEventQueue.begin();
152 iter != mEventData->mEventQueue.end();
157 case Event::KEYBOARD_FOCUS_GAIN_EVENT:
159 OnKeyboardFocus( true );
162 case Event::KEYBOARD_FOCUS_LOST_EVENT:
164 OnKeyboardFocus( false );
167 case Event::CURSOR_KEY_EVENT:
169 OnCursorKeyEvent( *iter );
172 case Event::TAP_EVENT:
177 case Event::PAN_EVENT:
182 case Event::GRAB_HANDLE_EVENT:
183 case Event::LEFT_SELECTION_HANDLE_EVENT:
184 case Event::RIGHT_SELECTION_HANDLE_EVENT: // Fall through
186 OnHandleEvent( *iter );
193 // The cursor must also be repositioned after inserts into the model
194 if( mEventData->mUpdateCursorPosition )
196 // Updates the cursor position and scrolls the text to make it visible.
198 UpdateCursorPosition();
200 if( mEventData->mScrollAfterUpdateCursorPosition )
202 ScrollToMakeCursorVisible();
203 mEventData->mScrollAfterUpdateCursorPosition = false;
206 mEventData->mDecoratorUpdated = true;
207 mEventData->mUpdateCursorPosition = false;
209 else if( mEventData->mUpdateLeftSelectionPosition )
211 UpdateSelectionHandle( LEFT_SELECTION_HANDLE );
213 if( mEventData->mScrollAfterUpdateCursorPosition )
215 ScrollToMakeCursorVisible();
216 mEventData->mScrollAfterUpdateCursorPosition = false;
219 mEventData->mDecoratorUpdated = true;
220 mEventData->mUpdateLeftSelectionPosition = false;
222 else if( mEventData->mUpdateRightSelectionPosition )
224 UpdateSelectionHandle( RIGHT_SELECTION_HANDLE );
226 if( mEventData->mScrollAfterUpdateCursorPosition )
228 ScrollToMakeCursorVisible();
229 mEventData->mScrollAfterUpdateCursorPosition = false;
232 mEventData->mDecoratorUpdated = true;
233 mEventData->mUpdateRightSelectionPosition = false;
236 mEventData->mEventQueue.clear();
238 return mEventData->mDecoratorUpdated;
241 void Controller::Impl::UpdateModel( OperationsMask operationsRequired )
243 // Calculate the operations to be done.
244 const OperationsMask operations = static_cast<OperationsMask>( mOperationsPending & operationsRequired );
246 Vector<Character>& utf32Characters = mLogicalModel->mText;
248 const Length numberOfCharacters = mLogicalModel->GetNumberOfCharacters();
250 Vector<LineBreakInfo>& lineBreakInfo = mLogicalModel->mLineBreakInfo;
251 if( GET_LINE_BREAKS & operations )
253 // Retrieves the line break info. The line break info is used to split the text in 'paragraphs' to
254 // calculate the bidirectional info for each 'paragraph'.
255 // It's also used to layout the text (where it should be a new line) or to shape the text (text in different lines
256 // is not shaped together).
257 lineBreakInfo.Resize( numberOfCharacters, TextAbstraction::LINE_NO_BREAK );
259 SetLineBreakInfo( utf32Characters,
263 Vector<WordBreakInfo>& wordBreakInfo = mLogicalModel->mWordBreakInfo;
264 if( GET_WORD_BREAKS & operations )
266 // Retrieves the word break info. The word break info is used to layout the text (where to wrap the text in lines).
267 wordBreakInfo.Resize( numberOfCharacters, TextAbstraction::WORD_NO_BREAK );
269 SetWordBreakInfo( utf32Characters,
273 const bool getScripts = GET_SCRIPTS & operations;
274 const bool validateFonts = VALIDATE_FONTS & operations;
276 Vector<ScriptRun>& scripts = mLogicalModel->mScriptRuns;
277 Vector<FontRun>& validFonts = mLogicalModel->mFontRuns;
279 if( getScripts || validateFonts )
281 // Validates the fonts assigned by the application or assigns default ones.
282 // It makes sure all the characters are going to be rendered by the correct font.
283 MultilanguageSupport multilanguageSupport = MultilanguageSupport::Get();
287 // Retrieves the scripts used in the text.
288 multilanguageSupport.SetScripts( utf32Characters,
295 if( 0u == validFonts.Count() )
297 // Copy the requested font defaults received via the property system.
298 // These may not be valid i.e. may not contain glyphs for the necessary scripts.
299 GetDefaultFonts( validFonts, numberOfCharacters );
302 // Validates the fonts. If there is a character with no assigned font it sets a default one.
303 // After this call, fonts are validated.
304 multilanguageSupport.ValidateFonts( utf32Characters,
310 Vector<Character> mirroredUtf32Characters;
311 bool textMirrored = false;
312 if( BIDI_INFO & operations )
314 // Count the number of LINE_NO_BREAK to reserve some space for the vector of paragraph's
315 // bidirectional info.
317 Length numberOfParagraphs = 0u;
319 const TextAbstraction::LineBreakInfo* lineBreakInfoBuffer = lineBreakInfo.Begin();
320 for( Length index = 0u; index < numberOfCharacters; ++index )
322 if( TextAbstraction::LINE_NO_BREAK == *( lineBreakInfoBuffer + index ) )
324 ++numberOfParagraphs;
328 Vector<BidirectionalParagraphInfoRun>& bidirectionalInfo = mLogicalModel->mBidirectionalParagraphInfo;
329 bidirectionalInfo.Reserve( numberOfParagraphs );
331 // Calculates the bidirectional info for the whole paragraph if it contains right to left scripts.
332 SetBidirectionalInfo( utf32Characters,
337 if( 0u != bidirectionalInfo.Count() )
339 // This paragraph has right to left text. Some characters may need to be mirrored.
340 // TODO: consider if the mirrored string can be stored as well.
342 textMirrored = GetMirroredText( utf32Characters, mirroredUtf32Characters );
344 // Only set the character directions if there is right to left characters.
345 Vector<CharacterDirection>& directions = mLogicalModel->mCharacterDirections;
346 directions.Resize( numberOfCharacters );
348 GetCharactersDirection( bidirectionalInfo,
353 // There is no right to left characters. Clear the directions vector.
354 mLogicalModel->mCharacterDirections.Clear();
359 Vector<GlyphInfo>& glyphs = mVisualModel->mGlyphs;
360 Vector<CharacterIndex>& glyphsToCharactersMap = mVisualModel->mGlyphsToCharacters;
361 Vector<Length>& charactersPerGlyph = mVisualModel->mCharactersPerGlyph;
362 if( SHAPE_TEXT & operations )
364 const Vector<Character>& textToShape = textMirrored ? mirroredUtf32Characters : utf32Characters;
366 ShapeText( textToShape,
371 glyphsToCharactersMap,
372 charactersPerGlyph );
374 // Create the 'number of glyphs' per character and the glyph to character conversion tables.
375 mVisualModel->CreateGlyphsPerCharacterTable( numberOfCharacters );
376 mVisualModel->CreateCharacterToGlyphTable( numberOfCharacters );
379 const Length numberOfGlyphs = glyphs.Count();
381 if( GET_GLYPH_METRICS & operations )
383 mFontClient.GetGlyphMetrics( glyphs.Begin(), numberOfGlyphs );
387 void Controller::Impl::GetDefaultFonts( Vector<FontRun>& fonts, Length numberOfCharacters )
392 fontRun.characterRun.characterIndex = 0;
393 fontRun.characterRun.numberOfCharacters = numberOfCharacters;
394 fontRun.fontId = mFontDefaults->GetFontId( mFontClient );
395 fontRun.isDefault = true;
397 fonts.PushBack( fontRun );
401 void Controller::Impl::OnKeyboardFocus( bool hasFocus )
403 if( NULL == mEventData )
405 // Nothing to do if there is no text input.
411 ChangeState( EventData::INACTIVE );
415 ChangeState( EventData::EDITING );
419 void Controller::Impl::OnCursorKeyEvent( const Event& event )
421 if( NULL == mEventData )
423 // Nothing to do if there is no text input.
427 int keyCode = event.p1.mInt;
429 if( Dali::DALI_KEY_CURSOR_LEFT == keyCode )
431 if( mEventData->mPrimaryCursorPosition > 0u )
433 mEventData->mPrimaryCursorPosition = CalculateNewCursorIndex( mEventData->mPrimaryCursorPosition - 1u );
436 else if( Dali::DALI_KEY_CURSOR_RIGHT == keyCode )
438 if( mLogicalModel->GetNumberOfCharacters() > mEventData->mPrimaryCursorPosition )
440 mEventData->mPrimaryCursorPosition = CalculateNewCursorIndex( mEventData->mPrimaryCursorPosition );
443 else if( Dali::DALI_KEY_CURSOR_UP == keyCode )
447 else if( Dali::DALI_KEY_CURSOR_DOWN == keyCode )
452 mEventData->mUpdateCursorPosition = true;
453 mEventData->mScrollAfterUpdateCursorPosition = true;
456 void Controller::Impl::OnTapEvent( const Event& event )
458 if( NULL != mEventData )
460 const unsigned int tapCount = event.p1.mUint;
464 if( ! IsShowingPlaceholderText() )
466 const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
467 const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
469 mEventData->mPrimaryCursorPosition = GetClosestCursorIndex( xPosition,
474 mEventData->mPrimaryCursorPosition = 0u;
477 mEventData->mUpdateCursorPosition = true;
478 mEventData->mScrollAfterUpdateCursorPosition = true;
480 else if( mEventData->mSelectionEnabled &&
483 RepositionSelectionHandles( event.p2.mFloat, event.p3.mFloat );
488 void Controller::Impl::OnPanEvent( const Event& event )
490 if( NULL == mEventData )
492 // Nothing to do if there is no text input.
496 int state = event.p1.mInt;
498 if( Gesture::Started == state ||
499 Gesture::Continuing == state )
501 const Vector2& actualSize = mVisualModel->GetActualSize();
502 const Vector2 currentScroll = mEventData->mScrollPosition;
504 if( mEventData->mHorizontalScrollingEnabled )
506 const float displacementX = event.p2.mFloat;
507 mEventData->mScrollPosition.x += displacementX;
509 ClampHorizontalScroll( actualSize );
512 if( mEventData->mVerticalScrollingEnabled )
514 const float displacementY = event.p3.mFloat;
515 mEventData->mScrollPosition.y += displacementY;
517 ClampVerticalScroll( actualSize );
520 if( mEventData->mDecorator )
522 mEventData->mDecorator->UpdatePositions( mEventData->mScrollPosition - currentScroll );
527 void Controller::Impl::OnHandleEvent( const Event& event )
529 if( NULL == mEventData )
531 // Nothing to do if there is no text input.
535 const unsigned int state = event.p1.mUint;
537 if( HANDLE_PRESSED == state )
539 // The event.p2 and event.p3 are in decorator coords. Need to transforms to text coords.
540 const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
541 const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
543 const CharacterIndex handleNewPosition = GetClosestCursorIndex( xPosition, yPosition );
545 if( Event::GRAB_HANDLE_EVENT == event.type )
547 ChangeState ( EventData::EDITING );
549 if( handleNewPosition != mEventData->mPrimaryCursorPosition )
551 mEventData->mPrimaryCursorPosition = handleNewPosition;
552 mEventData->mUpdateCursorPosition = true;
555 else if( Event::LEFT_SELECTION_HANDLE_EVENT == event.type )
557 if( handleNewPosition != mEventData->mLeftSelectionPosition )
559 mEventData->mLeftSelectionPosition = handleNewPosition;
560 mEventData->mUpdateLeftSelectionPosition = true;
563 else if( Event::RIGHT_SELECTION_HANDLE_EVENT == event.type )
565 if( handleNewPosition != mEventData->mRightSelectionPosition )
567 mEventData->mRightSelectionPosition = handleNewPosition;
568 mEventData->mUpdateRightSelectionPosition = true;
572 else if( ( HANDLE_RELEASED == state ) ||
573 ( HANDLE_STOP_SCROLLING == state ) )
575 if( mEventData->mGrabHandlePopupEnabled )
577 ChangeState( EventData::EDITING_WITH_POPUP );
579 if( Event::GRAB_HANDLE_EVENT == event.type )
581 mEventData->mUpdateCursorPosition = true;
583 if( HANDLE_STOP_SCROLLING == state )
585 // The event.p2 and event.p3 are in decorator coords. Need to transforms to text coords.
586 const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
587 const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
589 mEventData->mPrimaryCursorPosition = GetClosestCursorIndex( xPosition, yPosition );
591 mEventData->mScrollAfterUpdateCursorPosition = true;
594 mEventData->mDecoratorUpdated = true;
596 else if( HANDLE_SCROLLING == state )
598 const float xSpeed = event.p2.mFloat;
599 const Vector2& actualSize = mVisualModel->GetActualSize();
601 mEventData->mScrollPosition.x += xSpeed;
603 ClampHorizontalScroll( actualSize );
605 mEventData->mDecoratorUpdated = true;
609 void Controller::Impl::RepositionSelectionHandles( float visualX, float visualY )
611 if( NULL == mEventData )
613 // Nothing to do if there is no text input.
617 // TODO - Find which word was selected
619 const Vector<GlyphInfo>& glyphs = mVisualModel->mGlyphs;
620 const Vector<Vector2>::SizeType glyphCount = glyphs.Count();
622 const Vector<Vector2>& positions = mVisualModel->mGlyphPositions;
623 const Vector<Vector2>::SizeType positionCount = positions.Count();
625 // Guard against glyphs which did not fit inside the layout
626 const Vector<Vector2>::SizeType count = (positionCount < glyphCount) ? positionCount : glyphCount;
630 float primaryX = positions[0].x + mEventData->mScrollPosition.x;
631 float secondaryX = positions[count-1].x + glyphs[count-1].width + mEventData->mScrollPosition.x;
633 // TODO - multi-line selection
634 const Vector<LineRun>& lines = mVisualModel->mLines;
635 float height = lines.Count() ? lines[0].ascender + -lines[0].descender : 0.0f;
637 mEventData->mDecorator->SetPosition( LEFT_SELECTION_HANDLE, primaryX, mEventData->mScrollPosition.y, height );
638 mEventData->mDecorator->SetPosition( RIGHT_SELECTION_HANDLE, secondaryX, mEventData->mScrollPosition.y, height );
640 mEventData->mDecorator->ClearHighlights();
641 mEventData->mDecorator->AddHighlight( primaryX, mEventData->mScrollPosition.y, secondaryX, height + mEventData->mScrollPosition.y );
645 void Controller::Impl::ChangeState( EventData::State newState )
647 if( NULL == mEventData )
649 // Nothing to do if there is no text input.
653 if( mEventData->mState != newState )
655 mEventData->mState = newState;
657 if( EventData::INACTIVE == mEventData->mState )
659 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
660 mEventData->mDecorator->StopCursorBlink();
661 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
662 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
663 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
664 mEventData->mDecorator->SetPopupActive( false );
665 mEventData->mDecoratorUpdated = true;
667 else if ( EventData::SELECTING == mEventData->mState )
669 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
670 mEventData->mDecorator->StopCursorBlink();
671 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
672 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, true );
673 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, true );
674 mEventData->mDecoratorUpdated = true;
676 else if( EventData::EDITING == mEventData->mState )
678 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
679 if( mEventData->mCursorBlinkEnabled )
681 mEventData->mDecorator->StartCursorBlink();
683 // Grab handle is not shown until a tap is received whilst EDITING
684 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
685 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
686 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
687 mEventData->mDecoratorUpdated = true;
689 else if( EventData::EDITING_WITH_POPUP == mEventData->mState )
691 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
692 if( mEventData->mCursorBlinkEnabled )
694 mEventData->mDecorator->StartCursorBlink();
696 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
697 if( mEventData->mSelectionEnabled )
699 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, true );
700 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, true );
702 if( mEventData->mGrabHandlePopupEnabled )
704 mEventData->mDecorator->SetPopupActive( true );
706 mEventData->mDecoratorUpdated = true;
711 LineIndex Controller::Impl::GetClosestLine( float y ) const
713 float totalHeight = 0.f;
714 LineIndex lineIndex = 0u;
716 const Vector<LineRun>& lines = mVisualModel->mLines;
717 for( LineIndex endLine = lines.Count();
721 const LineRun& lineRun = lines[lineIndex];
722 totalHeight += lineRun.ascender + -lineRun.descender;
723 if( y < totalHeight )
732 CharacterIndex Controller::Impl::GetClosestCursorIndex( float visualX,
735 if( NULL == mEventData )
737 // Nothing to do if there is no text input.
741 CharacterIndex logicalIndex = 0u;
743 const Length numberOfGlyphs = mVisualModel->mGlyphs.Count();
744 const Length numberOfLines = mVisualModel->mLines.Count();
745 if( 0 == numberOfGlyphs ||
751 // Find which line is closest
752 const LineIndex lineIndex = GetClosestLine( visualY );
753 const LineRun& line = mVisualModel->mLines[lineIndex];
755 // Get the positions of the glyphs.
756 const Vector<Vector2>& positions = mVisualModel->mGlyphPositions;
757 const Vector2* const positionsBuffer = positions.Begin();
759 // Get the visual to logical conversion tables.
760 const CharacterIndex* const visualToLogicalBuffer = ( 0u != mLogicalModel->mVisualToLogicalMap.Count() ) ? mLogicalModel->mVisualToLogicalMap.Begin() : NULL;
761 const CharacterIndex* const visualToLogicalCursorBuffer = mLogicalModel->mVisualToLogicalCursorMap.Begin();
763 // Get the character to glyph conversion table.
764 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
766 // Get the glyphs per character table.
767 const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
769 // If the vector is void, there is no right to left characters.
770 const bool hasRightToLeftCharacters = NULL != visualToLogicalBuffer;
772 const CharacterIndex startCharacter = line.characterRun.characterIndex;
773 const CharacterIndex endCharacter = line.characterRun.characterIndex + line.characterRun.numberOfCharacters;
774 DALI_ASSERT_DEBUG( endCharacter <= mLogicalModel->mText.Count() && "Invalid line info" );
776 // Whether there is a hit on a glyph.
777 bool matched = false;
779 // Traverses glyphs in visual order. To do that use the visual to logical conversion table.
780 CharacterIndex visualIndex = startCharacter;
781 for( ; !matched && ( visualIndex < endCharacter ); ++visualIndex )
783 // The character in logical order.
784 const CharacterIndex characterLogicalOrderIndex = hasRightToLeftCharacters ? *( visualToLogicalBuffer + visualIndex ) : visualIndex;
786 // The first glyph for that character in logical order.
787 const GlyphIndex glyphLogicalOrderIndex = *( charactersToGlyphBuffer + characterLogicalOrderIndex );
789 // The number of glyphs for that character
790 const Length numberOfGlyphs = *( glyphsPerCharacterBuffer + characterLogicalOrderIndex );
792 // Get the metrics for the group of glyphs.
793 GlyphMetrics glyphMetrics;
794 GetGlyphsMetrics( glyphLogicalOrderIndex,
800 const Vector2& position = *( positionsBuffer + glyphLogicalOrderIndex );
802 const float glyphX = -glyphMetrics.xBearing + position.x + 0.5f * glyphMetrics.advance;
804 if( visualX < glyphX )
811 // Return the logical position of the cursor in characters.
815 visualIndex = endCharacter;
818 return hasRightToLeftCharacters ? *( visualToLogicalCursorBuffer + visualIndex ) : visualIndex;
821 void Controller::Impl::GetCursorPosition( CharacterIndex logical,
822 CursorInfo& cursorInfo )
824 // TODO: Check for multiline with \n, etc...
826 // Check if the logical position is the first or the last one of the text.
827 const bool isFirstPosition = 0u == logical;
828 const bool isLastPosition = mLogicalModel->GetNumberOfCharacters() == logical;
830 if( isFirstPosition && isLastPosition )
832 // There is zero characters. Get the default font.
834 FontId defaultFontId = 0u;
835 if( NULL == mFontDefaults )
837 defaultFontId = mFontClient.GetFontId( EMPTY_STRING,
842 defaultFontId = mFontDefaults->GetFontId( mFontClient );
845 Text::FontMetrics fontMetrics;
846 mFontClient.GetFontMetrics( defaultFontId, fontMetrics );
848 cursorInfo.lineHeight = fontMetrics.ascender - fontMetrics.descender;
849 cursorInfo.primaryCursorHeight = cursorInfo.lineHeight;
851 cursorInfo.primaryPosition.x = 0.f;
852 cursorInfo.primaryPosition.y = 0.f;
854 // Nothing else to do.
858 // Get the previous logical index.
859 const CharacterIndex previousLogical = isFirstPosition ? 0u : logical - 1u;
861 // Decrease the logical index if it's the last one.
867 // Get the direction of the character and the previous one.
868 const CharacterDirection* const modelCharacterDirectionsBuffer = ( 0u != mLogicalModel->mCharacterDirections.Count() ) ? mLogicalModel->mCharacterDirections.Begin() : NULL;
870 CharacterDirection isCurrentRightToLeft = false;
871 CharacterDirection isPreviousRightToLeft = false;
872 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
874 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + logical );
875 isPreviousRightToLeft = *( modelCharacterDirectionsBuffer + previousLogical );
878 // Get the line where the character is laid-out.
879 const LineRun* modelLines = mVisualModel->mLines.Begin();
881 const LineIndex lineIndex = mVisualModel->GetLineOfCharacter( logical );
882 const LineRun& line = *( modelLines + lineIndex );
884 // Get the paragraph's direction.
885 const CharacterDirection isRightToLeftParagraph = line.direction;
887 // Check whether there is an alternative position:
889 cursorInfo.isSecondaryCursor = ( isCurrentRightToLeft != isPreviousRightToLeft ) ||
890 ( isLastPosition && ( isRightToLeftParagraph != isCurrentRightToLeft ) );
892 // Set the line height.
893 cursorInfo.lineHeight = line.ascender + -line.descender;
895 // Convert the cursor position into the glyph position.
896 CharacterIndex characterIndex = logical;
897 if( cursorInfo.isSecondaryCursor &&
898 ( isRightToLeftParagraph != isCurrentRightToLeft ) )
900 characterIndex = previousLogical;
903 const GlyphIndex currentGlyphIndex = *( mVisualModel->mCharactersToGlyph.Begin() + characterIndex );
904 const Length numberOfGlyphs = *( mVisualModel->mGlyphsPerCharacter.Begin() + characterIndex );
905 const Length numberOfCharacters = *( mVisualModel->mCharactersPerGlyph.Begin() +currentGlyphIndex );
907 // Get the metrics for the group of glyphs.
908 GlyphMetrics glyphMetrics;
909 GetGlyphsMetrics( currentGlyphIndex,
915 float interGlyphAdvance = 0.f;
916 if( !isLastPosition &&
917 ( numberOfCharacters > 1u ) )
919 const CharacterIndex firstIndex = *( mVisualModel->mGlyphsToCharacters.Begin() + currentGlyphIndex );
920 interGlyphAdvance = static_cast<float>( characterIndex - firstIndex ) * glyphMetrics.advance / static_cast<float>( numberOfCharacters );
923 // Get the glyph position and x bearing.
924 const Vector2& currentPosition = *( mVisualModel->mGlyphPositions.Begin() + currentGlyphIndex );
926 // Set the cursor's height.
927 cursorInfo.primaryCursorHeight = glyphMetrics.fontHeight;
930 cursorInfo.primaryPosition.x = -glyphMetrics.xBearing + currentPosition.x + ( isCurrentRightToLeft ? glyphMetrics.advance : interGlyphAdvance );
931 cursorInfo.primaryPosition.y = line.ascender - glyphMetrics.ascender;
935 // The position of the cursor after the last character needs special
936 // care depending on its direction and the direction of the paragraph.
938 if( cursorInfo.isSecondaryCursor )
940 // Need to find the first character after the last character with the paragraph's direction.
941 // i.e l0 l1 l2 r0 r1 should find r0.
943 // TODO: check for more than one line!
944 characterIndex = isRightToLeftParagraph ? line.characterRun.characterIndex : line.characterRun.characterIndex + line.characterRun.numberOfCharacters - 1u;
945 characterIndex = mLogicalModel->GetLogicalCharacterIndex( characterIndex );
947 const GlyphIndex glyphIndex = *( mVisualModel->mCharactersToGlyph.Begin() + characterIndex );
948 const Length numberOfGlyphs = *( mVisualModel->mGlyphsPerCharacter.Begin() + characterIndex );
950 const Vector2& position = *( mVisualModel->mGlyphPositions.Begin() + glyphIndex );
952 // Get the metrics for the group of glyphs.
953 GlyphMetrics glyphMetrics;
954 GetGlyphsMetrics( glyphIndex,
960 cursorInfo.primaryPosition.x = -glyphMetrics.xBearing + position.x + ( isRightToLeftParagraph ? 0.f : glyphMetrics.advance );
962 cursorInfo.primaryPosition.y = line.ascender - glyphMetrics.ascender;
966 if( !isCurrentRightToLeft )
968 cursorInfo.primaryPosition.x += glyphMetrics.advance;
972 cursorInfo.primaryPosition.x -= glyphMetrics.advance;
977 // Set the alternative cursor position.
978 if( cursorInfo.isSecondaryCursor )
980 // Convert the cursor position into the glyph position.
981 const CharacterIndex previousCharacterIndex = ( ( isRightToLeftParagraph != isCurrentRightToLeft ) ? logical : previousLogical );
982 const GlyphIndex previousGlyphIndex = *( mVisualModel->mCharactersToGlyph.Begin() + previousCharacterIndex );
983 const Length numberOfGlyphs = *( mVisualModel->mGlyphsPerCharacter.Begin() + previousCharacterIndex );
985 // Get the glyph position.
986 const Vector2& previousPosition = *( mVisualModel->mGlyphPositions.Begin() + previousGlyphIndex );
988 // Get the metrics for the group of glyphs.
989 GlyphMetrics glyphMetrics;
990 GetGlyphsMetrics( previousGlyphIndex,
996 // Set the cursor position and height.
997 cursorInfo.secondaryPosition.x = -glyphMetrics.xBearing + previousPosition.x + ( ( ( isLastPosition && !isCurrentRightToLeft ) ||
998 ( !isLastPosition && isCurrentRightToLeft ) ) ? glyphMetrics.advance : 0.f );
1000 cursorInfo.secondaryCursorHeight = 0.5f * glyphMetrics.fontHeight;
1002 cursorInfo.secondaryPosition.y = cursorInfo.lineHeight - cursorInfo.secondaryCursorHeight - line.descender - ( glyphMetrics.fontHeight - glyphMetrics.ascender );
1004 // Update the primary cursor height as well.
1005 cursorInfo.primaryCursorHeight *= 0.5f;
1009 CharacterIndex Controller::Impl::CalculateNewCursorIndex( CharacterIndex index ) const
1011 if( NULL == mEventData )
1013 // Nothing to do if there is no text input.
1017 CharacterIndex cursorIndex = mEventData->mPrimaryCursorPosition;
1019 const Script script = mLogicalModel->GetScript( index );
1020 const GlyphIndex* charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
1021 const Length* charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin();
1023 Length numberOfCharacters = 0u;
1024 if( TextAbstraction::LATIN == script )
1026 // Prevents to jump the whole Latin ligatures like fi, ff, ...
1027 numberOfCharacters = 1u;
1031 GlyphIndex glyphIndex = *( charactersToGlyphBuffer + index );
1032 numberOfCharacters = *( charactersPerGlyphBuffer + glyphIndex );
1034 while( 0u == numberOfCharacters )
1036 numberOfCharacters = *( charactersPerGlyphBuffer + glyphIndex );
1041 if( index < mEventData->mPrimaryCursorPosition )
1043 cursorIndex -= numberOfCharacters;
1047 cursorIndex += numberOfCharacters;
1053 void Controller::Impl::UpdateCursorPosition()
1055 if( NULL == mEventData )
1057 // Nothing to do if there is no text input.
1061 CursorInfo cursorInfo;
1062 GetCursorPosition( mEventData->mPrimaryCursorPosition,
1065 const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset;
1066 const Vector2 cursorPosition = cursorInfo.primaryPosition + offset;
1068 // Sets the cursor position.
1069 mEventData->mDecorator->SetPosition( PRIMARY_CURSOR,
1072 cursorInfo.primaryCursorHeight,
1073 cursorInfo.lineHeight );
1075 // Sets the grab handle position.
1076 mEventData->mDecorator->SetPosition( GRAB_HANDLE,
1079 cursorInfo.lineHeight );
1081 if( cursorInfo.isSecondaryCursor )
1083 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_BOTH );
1084 mEventData->mDecorator->SetPosition( SECONDARY_CURSOR,
1085 cursorInfo.secondaryPosition.x + offset.x,
1086 cursorInfo.secondaryPosition.y + offset.y,
1087 cursorInfo.secondaryCursorHeight,
1088 cursorInfo.lineHeight );
1092 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1096 void Controller::Impl::UpdateSelectionHandle( HandleType handleType )
1098 if( ( LEFT_SELECTION_HANDLE != handleType ) &&
1099 ( RIGHT_SELECTION_HANDLE != handleType ) )
1104 const bool leftSelectionHandle = LEFT_SELECTION_HANDLE == handleType;
1105 const CharacterIndex index = leftSelectionHandle ? mEventData->mLeftSelectionPosition : mEventData->mRightSelectionPosition;
1107 CursorInfo cursorInfo;
1108 GetCursorPosition( index,
1111 const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset;
1112 const Vector2 cursorPosition = cursorInfo.primaryPosition + offset;
1114 // Sets the grab handle position.
1115 mEventData->mDecorator->SetPosition( handleType,
1118 cursorInfo.lineHeight );
1121 void Controller::Impl::ClampHorizontalScroll( const Vector2& actualSize )
1123 // Clamp between -space & 0 (and the text alignment).
1124 if( actualSize.width > mControlSize.width )
1126 const float space = ( actualSize.width - mControlSize.width ) + mAlignmentOffset.x;
1127 mEventData->mScrollPosition.x = ( mEventData->mScrollPosition.x < -space ) ? -space : mEventData->mScrollPosition.x;
1128 mEventData->mScrollPosition.x = ( mEventData->mScrollPosition.x > -mAlignmentOffset.x ) ? -mAlignmentOffset.x : mEventData->mScrollPosition.x;
1130 mEventData->mDecoratorUpdated = true;
1134 mEventData->mScrollPosition.x = 0.f;
1138 void Controller::Impl::ClampVerticalScroll( const Vector2& actualSize )
1140 // Clamp between -space & 0 (and the text alignment).
1141 if( actualSize.height > mControlSize.height )
1143 const float space = ( actualSize.height - mControlSize.height ) + mAlignmentOffset.y;
1144 mEventData->mScrollPosition.y = ( mEventData->mScrollPosition.y < -space ) ? -space : mEventData->mScrollPosition.y;
1145 mEventData->mScrollPosition.y = ( mEventData->mScrollPosition.y > -mAlignmentOffset.y ) ? -mAlignmentOffset.y : mEventData->mScrollPosition.y;
1147 mEventData->mDecoratorUpdated = true;
1151 mEventData->mScrollPosition.y = 0.f;
1155 void Controller::Impl::ScrollToMakeCursorVisible()
1157 if( NULL == mEventData )
1159 // Nothing to do if there is no text input.
1163 const Vector2& primaryCursorPosition = mEventData->mDecorator->GetPosition( PRIMARY_CURSOR );
1166 bool updateDecorator = false;
1167 if( primaryCursorPosition.x < 0.f )
1169 offset.x = -primaryCursorPosition.x;
1170 mEventData->mScrollPosition.x += offset.x;
1171 updateDecorator = true;
1173 else if( primaryCursorPosition.x > mControlSize.width )
1175 offset.x = mControlSize.width - primaryCursorPosition.x;
1176 mEventData->mScrollPosition.x += offset.x;
1177 updateDecorator = true;
1180 if( updateDecorator && mEventData->mDecorator )
1182 mEventData->mDecorator->UpdatePositions( offset );
1185 // TODO : calculate the vertical scroll.
1188 void Controller::Impl::RequestRelayout()
1190 mControlInterface.RequestTextRelayout();
1195 } // namespace Toolkit