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 // Nothing to do if there is no text input.
464 const unsigned int tapCount = event.p1.mUint;
468 // Grab handle is not shown until a tap is received whilst EDITING
469 if( EventData::EDITING == mEventData->mState &&
470 !IsShowingPlaceholderText() )
472 if( mEventData->mGrabHandleEnabled )
474 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, true );
476 mEventData->mDecorator->SetPopupActive( false );
479 ChangeState( EventData::EDITING );
481 const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
482 const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
484 mEventData->mPrimaryCursorPosition = GetClosestCursorIndex( xPosition,
487 mEventData->mUpdateCursorPosition = true;
488 mEventData->mScrollAfterUpdateCursorPosition = true;
490 else if( mEventData->mSelectionEnabled &&
493 ChangeState( EventData::SELECTING );
495 RepositionSelectionHandles( event.p2.mFloat, event.p3.mFloat );
499 void Controller::Impl::OnPanEvent( const Event& event )
501 if( NULL == mEventData )
503 // Nothing to do if there is no text input.
507 int state = event.p1.mInt;
509 if( Gesture::Started == state ||
510 Gesture::Continuing == state )
512 const Vector2& actualSize = mVisualModel->GetActualSize();
513 const Vector2 currentScroll = mEventData->mScrollPosition;
515 if( mEventData->mHorizontalScrollingEnabled )
517 const float displacementX = event.p2.mFloat;
518 mEventData->mScrollPosition.x += displacementX;
520 ClampHorizontalScroll( actualSize );
523 if( mEventData->mVerticalScrollingEnabled )
525 const float displacementY = event.p3.mFloat;
526 mEventData->mScrollPosition.y += displacementY;
528 ClampVerticalScroll( actualSize );
531 if( mEventData->mDecorator )
533 mEventData->mDecorator->UpdatePositions( mEventData->mScrollPosition - currentScroll );
538 void Controller::Impl::OnHandleEvent( const Event& event )
540 if( NULL == mEventData )
542 // Nothing to do if there is no text input.
546 const unsigned int state = event.p1.mUint;
548 if( HANDLE_PRESSED == state )
550 // The event.p2 and event.p3 are in decorator coords. Need to transforms to text coords.
551 const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
552 const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
554 const CharacterIndex handleNewPosition = GetClosestCursorIndex( xPosition, yPosition );
556 if( Event::GRAB_HANDLE_EVENT == event.type )
558 ChangeState ( EventData::EDITING );
560 if( handleNewPosition != mEventData->mPrimaryCursorPosition )
562 mEventData->mPrimaryCursorPosition = handleNewPosition;
563 mEventData->mUpdateCursorPosition = true;
566 else if( Event::LEFT_SELECTION_HANDLE_EVENT == event.type )
568 if( handleNewPosition != mEventData->mLeftSelectionPosition )
570 mEventData->mLeftSelectionPosition = handleNewPosition;
571 mEventData->mUpdateLeftSelectionPosition = true;
574 else if( Event::RIGHT_SELECTION_HANDLE_EVENT == event.type )
576 if( handleNewPosition != mEventData->mRightSelectionPosition )
578 mEventData->mRightSelectionPosition = handleNewPosition;
579 mEventData->mUpdateRightSelectionPosition = true;
583 else if( ( HANDLE_RELEASED == state ) ||
584 ( HANDLE_STOP_SCROLLING == state ) )
586 if( mEventData->mGrabHandlePopupEnabled )
588 ChangeState( EventData::EDITING_WITH_POPUP );
590 if( Event::GRAB_HANDLE_EVENT == event.type )
592 mEventData->mUpdateCursorPosition = true;
594 if( HANDLE_STOP_SCROLLING == state )
596 // The event.p2 and event.p3 are in decorator coords. Need to transforms to text coords.
597 const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
598 const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
600 mEventData->mPrimaryCursorPosition = GetClosestCursorIndex( xPosition, yPosition );
602 mEventData->mScrollAfterUpdateCursorPosition = true;
605 mEventData->mDecoratorUpdated = true;
607 else if( HANDLE_SCROLLING == state )
609 const float xSpeed = event.p2.mFloat;
610 const Vector2& actualSize = mVisualModel->GetActualSize();
612 mEventData->mScrollPosition.x += xSpeed;
614 ClampHorizontalScroll( actualSize );
616 mEventData->mDecoratorUpdated = true;
620 void Controller::Impl::RepositionSelectionHandles( float visualX, float visualY )
622 if( NULL == mEventData )
624 // Nothing to do if there is no text input.
628 // TODO - Find which word was selected
630 const Vector<GlyphInfo>& glyphs = mVisualModel->mGlyphs;
631 const Vector<Vector2>::SizeType glyphCount = glyphs.Count();
633 const Vector<Vector2>& positions = mVisualModel->mGlyphPositions;
634 const Vector<Vector2>::SizeType positionCount = positions.Count();
636 // Guard against glyphs which did not fit inside the layout
637 const Vector<Vector2>::SizeType count = (positionCount < glyphCount) ? positionCount : glyphCount;
641 float primaryX = positions[0].x + mEventData->mScrollPosition.x;
642 float secondaryX = positions[count-1].x + glyphs[count-1].width + mEventData->mScrollPosition.x;
644 // TODO - multi-line selection
645 const Vector<LineRun>& lines = mVisualModel->mLines;
646 float height = lines.Count() ? lines[0].ascender + -lines[0].descender : 0.0f;
648 mEventData->mDecorator->SetPosition( LEFT_SELECTION_HANDLE, primaryX, mEventData->mScrollPosition.y, height );
649 mEventData->mDecorator->SetPosition( RIGHT_SELECTION_HANDLE, secondaryX, mEventData->mScrollPosition.y, height );
651 mEventData->mDecorator->ClearHighlights();
652 mEventData->mDecorator->AddHighlight( primaryX, mEventData->mScrollPosition.y, secondaryX, height + mEventData->mScrollPosition.y );
656 void Controller::Impl::ChangeState( EventData::State newState )
658 if( NULL == mEventData )
660 // Nothing to do if there is no text input.
664 if( mEventData->mState != newState )
666 mEventData->mState = newState;
668 if( EventData::INACTIVE == mEventData->mState )
670 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
671 mEventData->mDecorator->StopCursorBlink();
672 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
673 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
674 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
675 mEventData->mDecorator->SetPopupActive( false );
676 mEventData->mDecoratorUpdated = true;
678 else if ( EventData::SELECTING == mEventData->mState )
680 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
681 mEventData->mDecorator->StopCursorBlink();
682 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
683 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, true );
684 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, true );
685 mEventData->mDecoratorUpdated = true;
687 else if( EventData::EDITING == mEventData->mState )
689 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
690 if( mEventData->mCursorBlinkEnabled )
692 mEventData->mDecorator->StartCursorBlink();
694 // Grab handle is not shown until a tap is received whilst EDITING
695 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
696 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
697 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
698 mEventData->mDecoratorUpdated = true;
700 else if( EventData::EDITING_WITH_POPUP == mEventData->mState )
702 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
703 if( mEventData->mCursorBlinkEnabled )
705 mEventData->mDecorator->StartCursorBlink();
707 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
708 if( mEventData->mSelectionEnabled )
710 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, true );
711 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, true );
713 if( mEventData->mGrabHandlePopupEnabled )
715 mEventData->mDecorator->SetPopupActive( true );
717 mEventData->mDecoratorUpdated = true;
722 LineIndex Controller::Impl::GetClosestLine( float y ) const
724 float totalHeight = 0.f;
725 LineIndex lineIndex = 0u;
727 const Vector<LineRun>& lines = mVisualModel->mLines;
728 for( LineIndex endLine = lines.Count();
732 const LineRun& lineRun = lines[lineIndex];
733 totalHeight += lineRun.ascender + -lineRun.descender;
734 if( y < totalHeight )
743 CharacterIndex Controller::Impl::GetClosestCursorIndex( float visualX,
746 if( NULL == mEventData )
748 // Nothing to do if there is no text input.
752 CharacterIndex logicalIndex = 0u;
754 const Length numberOfGlyphs = mVisualModel->mGlyphs.Count();
755 const Length numberOfLines = mVisualModel->mLines.Count();
756 if( 0 == numberOfGlyphs ||
762 // Find which line is closest
763 const LineIndex lineIndex = GetClosestLine( visualY );
764 const LineRun& line = mVisualModel->mLines[lineIndex];
766 // Get the positions of the glyphs.
767 const Vector<Vector2>& positions = mVisualModel->mGlyphPositions;
768 const Vector2* const positionsBuffer = positions.Begin();
770 // Get the visual to logical conversion tables.
771 const CharacterIndex* const visualToLogicalBuffer = ( 0u != mLogicalModel->mVisualToLogicalMap.Count() ) ? mLogicalModel->mVisualToLogicalMap.Begin() : NULL;
772 const CharacterIndex* const visualToLogicalCursorBuffer = mLogicalModel->mVisualToLogicalCursorMap.Begin();
774 // Get the character to glyph conversion table.
775 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
777 // Get the glyphs per character table.
778 const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
780 // If the vector is void, there is no right to left characters.
781 const bool hasRightToLeftCharacters = NULL != visualToLogicalBuffer;
783 const CharacterIndex startCharacter = line.characterRun.characterIndex;
784 const CharacterIndex endCharacter = line.characterRun.characterIndex + line.characterRun.numberOfCharacters;
785 DALI_ASSERT_DEBUG( endCharacter <= mLogicalModel->mText.Count() && "Invalid line info" );
787 // Whether there is a hit on a glyph.
788 bool matched = false;
790 // Traverses glyphs in visual order. To do that use the visual to logical conversion table.
791 CharacterIndex visualIndex = startCharacter;
792 for( ; !matched && ( visualIndex < endCharacter ); ++visualIndex )
794 // The character in logical order.
795 const CharacterIndex characterLogicalOrderIndex = hasRightToLeftCharacters ? *( visualToLogicalBuffer + visualIndex ) : visualIndex;
797 // The first glyph for that character in logical order.
798 const GlyphIndex glyphLogicalOrderIndex = *( charactersToGlyphBuffer + characterLogicalOrderIndex );
800 // The number of glyphs for that character
801 const Length numberOfGlyphs = *( glyphsPerCharacterBuffer + characterLogicalOrderIndex );
803 // Get the metrics for the group of glyphs.
804 GlyphMetrics glyphMetrics;
805 GetGlyphsMetrics( glyphLogicalOrderIndex,
811 const Vector2& position = *( positionsBuffer + glyphLogicalOrderIndex );
813 const float glyphX = -glyphMetrics.xBearing + position.x + 0.5f * glyphMetrics.advance;
815 if( visualX < glyphX )
822 // Return the logical position of the cursor in characters.
826 visualIndex = endCharacter;
829 return hasRightToLeftCharacters ? *( visualToLogicalCursorBuffer + visualIndex ) : visualIndex;
832 void Controller::Impl::GetCursorPosition( CharacterIndex logical,
833 CursorInfo& cursorInfo )
835 // TODO: Check for multiline with \n, etc...
837 // Check if the logical position is the first or the last one of the text.
838 const bool isFirstPosition = 0u == logical;
839 const bool isLastPosition = mLogicalModel->GetNumberOfCharacters() == logical;
841 if( isFirstPosition && isLastPosition )
843 // There is zero characters. Get the default font.
845 FontId defaultFontId = 0u;
846 if( NULL == mFontDefaults )
848 defaultFontId = mFontClient.GetFontId( EMPTY_STRING,
853 defaultFontId = mFontDefaults->GetFontId( mFontClient );
856 Text::FontMetrics fontMetrics;
857 mFontClient.GetFontMetrics( defaultFontId, fontMetrics );
859 cursorInfo.lineHeight = fontMetrics.ascender - fontMetrics.descender;
860 cursorInfo.primaryCursorHeight = cursorInfo.lineHeight;
862 cursorInfo.primaryPosition.x = 0.f;
863 cursorInfo.primaryPosition.y = 0.f;
865 // Nothing else to do.
869 // Get the previous logical index.
870 const CharacterIndex previousLogical = isFirstPosition ? 0u : logical - 1u;
872 // Decrease the logical index if it's the last one.
878 // Get the direction of the character and the previous one.
879 const CharacterDirection* const modelCharacterDirectionsBuffer = ( 0u != mLogicalModel->mCharacterDirections.Count() ) ? mLogicalModel->mCharacterDirections.Begin() : NULL;
881 CharacterDirection isCurrentRightToLeft = false;
882 CharacterDirection isPreviousRightToLeft = false;
883 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
885 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + logical );
886 isPreviousRightToLeft = *( modelCharacterDirectionsBuffer + previousLogical );
889 // Get the line where the character is laid-out.
890 const LineRun* modelLines = mVisualModel->mLines.Begin();
892 const LineIndex lineIndex = mVisualModel->GetLineOfCharacter( logical );
893 const LineRun& line = *( modelLines + lineIndex );
895 // Get the paragraph's direction.
896 const CharacterDirection isRightToLeftParagraph = line.direction;
898 // Check whether there is an alternative position:
900 cursorInfo.isSecondaryCursor = ( isCurrentRightToLeft != isPreviousRightToLeft ) ||
901 ( isLastPosition && ( isRightToLeftParagraph != isCurrentRightToLeft ) );
903 // Set the line height.
904 cursorInfo.lineHeight = line.ascender + -line.descender;
906 // Convert the cursor position into the glyph position.
907 CharacterIndex characterIndex = logical;
908 if( cursorInfo.isSecondaryCursor &&
909 ( isRightToLeftParagraph != isCurrentRightToLeft ) )
911 characterIndex = previousLogical;
914 const GlyphIndex currentGlyphIndex = *( mVisualModel->mCharactersToGlyph.Begin() + characterIndex );
915 const Length numberOfGlyphs = *( mVisualModel->mGlyphsPerCharacter.Begin() + characterIndex );
916 const Length numberOfCharacters = *( mVisualModel->mCharactersPerGlyph.Begin() +currentGlyphIndex );
918 // Get the metrics for the group of glyphs.
919 GlyphMetrics glyphMetrics;
920 GetGlyphsMetrics( currentGlyphIndex,
926 float interGlyphAdvance = 0.f;
927 if( !isLastPosition &&
928 ( numberOfCharacters > 1u ) )
930 const CharacterIndex firstIndex = *( mVisualModel->mGlyphsToCharacters.Begin() + currentGlyphIndex );
931 interGlyphAdvance = static_cast<float>( characterIndex - firstIndex ) * glyphMetrics.advance / static_cast<float>( numberOfCharacters );
934 // Get the glyph position and x bearing.
935 const Vector2& currentPosition = *( mVisualModel->mGlyphPositions.Begin() + currentGlyphIndex );
937 // Set the cursor's height.
938 cursorInfo.primaryCursorHeight = glyphMetrics.fontHeight;
941 cursorInfo.primaryPosition.x = -glyphMetrics.xBearing + currentPosition.x + ( isCurrentRightToLeft ? glyphMetrics.advance : interGlyphAdvance );
942 cursorInfo.primaryPosition.y = line.ascender - glyphMetrics.ascender;
946 // The position of the cursor after the last character needs special
947 // care depending on its direction and the direction of the paragraph.
949 if( cursorInfo.isSecondaryCursor )
951 // Need to find the first character after the last character with the paragraph's direction.
952 // i.e l0 l1 l2 r0 r1 should find r0.
954 // TODO: check for more than one line!
955 characterIndex = isRightToLeftParagraph ? line.characterRun.characterIndex : line.characterRun.characterIndex + line.characterRun.numberOfCharacters - 1u;
956 characterIndex = mLogicalModel->GetLogicalCharacterIndex( characterIndex );
958 const GlyphIndex glyphIndex = *( mVisualModel->mCharactersToGlyph.Begin() + characterIndex );
959 const Length numberOfGlyphs = *( mVisualModel->mGlyphsPerCharacter.Begin() + characterIndex );
961 const Vector2& position = *( mVisualModel->mGlyphPositions.Begin() + glyphIndex );
963 // Get the metrics for the group of glyphs.
964 GlyphMetrics glyphMetrics;
965 GetGlyphsMetrics( glyphIndex,
971 cursorInfo.primaryPosition.x = -glyphMetrics.xBearing + position.x + ( isRightToLeftParagraph ? 0.f : glyphMetrics.advance );
973 cursorInfo.primaryPosition.y = line.ascender - glyphMetrics.ascender;
977 if( !isCurrentRightToLeft )
979 cursorInfo.primaryPosition.x += glyphMetrics.advance;
983 cursorInfo.primaryPosition.x -= glyphMetrics.advance;
988 // Set the alternative cursor position.
989 if( cursorInfo.isSecondaryCursor )
991 // Convert the cursor position into the glyph position.
992 const CharacterIndex previousCharacterIndex = ( ( isRightToLeftParagraph != isCurrentRightToLeft ) ? logical : previousLogical );
993 const GlyphIndex previousGlyphIndex = *( mVisualModel->mCharactersToGlyph.Begin() + previousCharacterIndex );
994 const Length numberOfGlyphs = *( mVisualModel->mGlyphsPerCharacter.Begin() + previousCharacterIndex );
996 // Get the glyph position.
997 const Vector2& previousPosition = *( mVisualModel->mGlyphPositions.Begin() + previousGlyphIndex );
999 // Get the metrics for the group of glyphs.
1000 GlyphMetrics glyphMetrics;
1001 GetGlyphsMetrics( previousGlyphIndex,
1007 // Set the cursor position and height.
1008 cursorInfo.secondaryPosition.x = -glyphMetrics.xBearing + previousPosition.x + ( ( ( isLastPosition && !isCurrentRightToLeft ) ||
1009 ( !isLastPosition && isCurrentRightToLeft ) ) ? glyphMetrics.advance : 0.f );
1011 cursorInfo.secondaryCursorHeight = 0.5f * glyphMetrics.fontHeight;
1013 cursorInfo.secondaryPosition.y = cursorInfo.lineHeight - cursorInfo.secondaryCursorHeight - line.descender - ( glyphMetrics.fontHeight - glyphMetrics.ascender );
1015 // Update the primary cursor height as well.
1016 cursorInfo.primaryCursorHeight *= 0.5f;
1020 CharacterIndex Controller::Impl::CalculateNewCursorIndex( CharacterIndex index ) const
1022 if( NULL == mEventData )
1024 // Nothing to do if there is no text input.
1028 CharacterIndex cursorIndex = mEventData->mPrimaryCursorPosition;
1030 const Script script = mLogicalModel->GetScript( index );
1031 const GlyphIndex* charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
1032 const Length* charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin();
1034 Length numberOfCharacters = 0u;
1035 if( TextAbstraction::LATIN == script )
1037 // Prevents to jump the whole Latin ligatures like fi, ff, ...
1038 numberOfCharacters = 1u;
1042 GlyphIndex glyphIndex = *( charactersToGlyphBuffer + index );
1043 numberOfCharacters = *( charactersPerGlyphBuffer + glyphIndex );
1045 while( 0u == numberOfCharacters )
1047 numberOfCharacters = *( charactersPerGlyphBuffer + glyphIndex );
1052 if( index < mEventData->mPrimaryCursorPosition )
1054 cursorIndex -= numberOfCharacters;
1058 cursorIndex += numberOfCharacters;
1064 void Controller::Impl::UpdateCursorPosition()
1066 if( NULL == mEventData )
1068 // Nothing to do if there is no text input.
1072 CursorInfo cursorInfo;
1073 GetCursorPosition( mEventData->mPrimaryCursorPosition,
1076 const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset;
1077 const Vector2 cursorPosition = cursorInfo.primaryPosition + offset;
1079 // Sets the cursor position.
1080 mEventData->mDecorator->SetPosition( PRIMARY_CURSOR,
1083 cursorInfo.primaryCursorHeight,
1084 cursorInfo.lineHeight );
1086 // Sets the grab handle position.
1087 mEventData->mDecorator->SetPosition( GRAB_HANDLE,
1090 cursorInfo.lineHeight );
1092 if( cursorInfo.isSecondaryCursor )
1094 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_BOTH );
1095 mEventData->mDecorator->SetPosition( SECONDARY_CURSOR,
1096 cursorInfo.secondaryPosition.x + offset.x,
1097 cursorInfo.secondaryPosition.y + offset.y,
1098 cursorInfo.secondaryCursorHeight,
1099 cursorInfo.lineHeight );
1103 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1107 void Controller::Impl::UpdateSelectionHandle( HandleType handleType )
1109 if( ( LEFT_SELECTION_HANDLE != handleType ) &&
1110 ( RIGHT_SELECTION_HANDLE != handleType ) )
1115 const bool leftSelectionHandle = LEFT_SELECTION_HANDLE == handleType;
1116 const CharacterIndex index = leftSelectionHandle ? mEventData->mLeftSelectionPosition : mEventData->mRightSelectionPosition;
1118 CursorInfo cursorInfo;
1119 GetCursorPosition( index,
1122 const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset;
1123 const Vector2 cursorPosition = cursorInfo.primaryPosition + offset;
1125 // Sets the grab handle position.
1126 mEventData->mDecorator->SetPosition( handleType,
1129 cursorInfo.lineHeight );
1132 void Controller::Impl::ClampHorizontalScroll( const Vector2& actualSize )
1134 // Clamp between -space & 0 (and the text alignment).
1135 if( actualSize.width > mControlSize.width )
1137 const float space = ( actualSize.width - mControlSize.width ) + mAlignmentOffset.x;
1138 mEventData->mScrollPosition.x = ( mEventData->mScrollPosition.x < -space ) ? -space : mEventData->mScrollPosition.x;
1139 mEventData->mScrollPosition.x = ( mEventData->mScrollPosition.x > -mAlignmentOffset.x ) ? -mAlignmentOffset.x : mEventData->mScrollPosition.x;
1141 mEventData->mDecoratorUpdated = true;
1145 mEventData->mScrollPosition.x = 0.f;
1149 void Controller::Impl::ClampVerticalScroll( const Vector2& actualSize )
1151 // Clamp between -space & 0 (and the text alignment).
1152 if( actualSize.height > mControlSize.height )
1154 const float space = ( actualSize.height - mControlSize.height ) + mAlignmentOffset.y;
1155 mEventData->mScrollPosition.y = ( mEventData->mScrollPosition.y < -space ) ? -space : mEventData->mScrollPosition.y;
1156 mEventData->mScrollPosition.y = ( mEventData->mScrollPosition.y > -mAlignmentOffset.y ) ? -mAlignmentOffset.y : mEventData->mScrollPosition.y;
1158 mEventData->mDecoratorUpdated = true;
1162 mEventData->mScrollPosition.y = 0.f;
1166 void Controller::Impl::ScrollToMakeCursorVisible()
1168 if( NULL == mEventData )
1170 // Nothing to do if there is no text input.
1174 const Vector2& primaryCursorPosition = mEventData->mDecorator->GetPosition( PRIMARY_CURSOR );
1177 bool updateDecorator = false;
1178 if( primaryCursorPosition.x < 0.f )
1180 offset.x = -primaryCursorPosition.x;
1181 mEventData->mScrollPosition.x += offset.x;
1182 updateDecorator = true;
1184 else if( primaryCursorPosition.x > mControlSize.width )
1186 offset.x = mControlSize.width - primaryCursorPosition.x;
1187 mEventData->mScrollPosition.x += offset.x;
1188 updateDecorator = true;
1191 if( updateDecorator && mEventData->mDecorator )
1193 mEventData->mDecorator->UpdatePositions( offset );
1196 // TODO : calculate the vertical scroll.
1199 void Controller::Impl::RequestRelayout()
1201 mControlInterface.RequestTextRelayout();
1206 } // namespace Toolkit