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>
23 #include <dali/integration-api/debug.h>
26 #include <dali-toolkit/internal/text/bidirectional-support.h>
27 #include <dali-toolkit/internal/text/character-set-conversion.h>
28 #include <dali-toolkit/internal/text/layouts/layout-parameters.h>
29 #include <dali-toolkit/internal/text/multi-language-support.h>
30 #include <dali-toolkit/internal/text/script-run.h>
31 #include <dali-toolkit/internal/text/segmentation.h>
32 #include <dali-toolkit/internal/text/shaper.h>
33 #include <dali-toolkit/internal/text/text-io.h>
34 #include <dali-toolkit/internal/text/text-view.h>
39 #if defined(DEBUG_ENABLED)
40 Debug::Filter* gLogFilter = Debug::Filter::New(Debug::Concise, true, "LOG_TEXT_CONTROLS");
44 * @brief Some characters can be shaped in more than one glyph.
45 * This struct is used to retrieve metrics from these group of glyphs.
59 float fontHeight; ///< The font's height of that glyphs.
60 float advance; ///< The sum of all the advances of all the glyphs.
61 float ascender; ///< The font's ascender.
62 float xBearing; ///< The x bearing of the first glyph.
65 const std::string EMPTY_STRING("");
79 * @brief Get some glyph's metrics of a group of glyphs formed as a result of shaping one character.
81 * @param[in] glyphIndex The index to the first glyph.
82 * @param[in] numberOfGlyphs The number of glyphs.
83 * @param[out] glyphMetrics Some glyph metrics (font height, advance, ascender and x bearing).
87 void GetGlyphsMetrics( GlyphIndex glyphIndex,
88 Length numberOfGlyphs,
89 GlyphMetrics& glyphMetrics,
90 VisualModelPtr visualModel,
91 TextAbstraction::FontClient& fontClient )
93 const GlyphInfo* glyphsBuffer = visualModel->mGlyphs.Begin();
95 const GlyphInfo& firstGlyph = *( glyphsBuffer + glyphIndex );
97 Text::FontMetrics fontMetrics;
98 fontClient.GetFontMetrics( firstGlyph.fontId, fontMetrics );
100 glyphMetrics.fontHeight = fontMetrics.height;
101 glyphMetrics.advance = firstGlyph.advance;
102 glyphMetrics.ascender = fontMetrics.ascender;
103 glyphMetrics.xBearing = firstGlyph.xBearing;
105 for( unsigned int i = 1u; i < numberOfGlyphs; ++i )
107 const GlyphInfo& glyphInfo = *( glyphsBuffer + glyphIndex + i );
109 glyphMetrics.advance += glyphInfo.advance;
113 EventData::EventData( DecoratorPtr decorator )
114 : mDecorator( decorator ),
115 mPlaceholderTextActive(),
116 mPlaceholderTextInactive(),
117 mPlaceholderTextColor( 0.8f, 0.8f, 0.8f, 0.8f ),
121 mPrimaryCursorPosition( 0u ),
122 mLeftSelectionPosition( 0u ),
123 mRightSelectionPosition( 0u ),
124 mPreEditStartPosition( 0u ),
125 mPreEditLength( 0u ),
126 mIsShowingPlaceholderText( false ),
127 mPreEditFlag( false ),
128 mDecoratorUpdated( false ),
129 mCursorBlinkEnabled( true ),
130 mGrabHandleEnabled( true ),
131 mGrabHandlePopupEnabled( false ),
132 mSelectionEnabled( false ),
133 mHorizontalScrollingEnabled( true ),
134 mVerticalScrollingEnabled( false ),
135 mUpdateCursorPosition( false ),
136 mUpdateLeftSelectionPosition( false ),
137 mUpdateRightSelectionPosition( false ),
138 mScrollAfterUpdateCursorPosition( false )
141 EventData::~EventData()
144 bool Controller::Impl::ProcessInputEvents()
146 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "-->Controller::ProcessInputEvents\n" );
147 if( NULL == mEventData )
149 // Nothing to do if there is no text input.
150 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::ProcessInputEvents no event data\n" );
154 mEventData->mDecoratorUpdated = false;
156 if( mEventData->mDecorator )
158 for( std::vector<Event>::iterator iter = mEventData->mEventQueue.begin();
159 iter != mEventData->mEventQueue.end();
164 case Event::KEYBOARD_FOCUS_GAIN_EVENT:
166 OnKeyboardFocus( true );
169 case Event::KEYBOARD_FOCUS_LOST_EVENT:
171 OnKeyboardFocus( false );
174 case Event::CURSOR_KEY_EVENT:
176 OnCursorKeyEvent( *iter );
179 case Event::TAP_EVENT:
184 case Event::PAN_EVENT:
189 case Event::GRAB_HANDLE_EVENT:
190 case Event::LEFT_SELECTION_HANDLE_EVENT:
191 case Event::RIGHT_SELECTION_HANDLE_EVENT: // Fall through
193 OnHandleEvent( *iter );
200 // The cursor must also be repositioned after inserts into the model
201 if( mEventData->mUpdateCursorPosition )
203 // Updates the cursor position and scrolls the text to make it visible.
205 UpdateCursorPosition();
207 if( mEventData->mScrollAfterUpdateCursorPosition )
209 ScrollToMakeCursorVisible();
210 mEventData->mScrollAfterUpdateCursorPosition = false;
213 mEventData->mDecoratorUpdated = true;
214 mEventData->mUpdateCursorPosition = false;
216 else if( mEventData->mUpdateLeftSelectionPosition )
218 UpdateSelectionHandle( LEFT_SELECTION_HANDLE );
220 if( mEventData->mScrollAfterUpdateCursorPosition )
222 ScrollToMakeCursorVisible();
223 mEventData->mScrollAfterUpdateCursorPosition = false;
226 mEventData->mDecoratorUpdated = true;
227 mEventData->mUpdateLeftSelectionPosition = false;
229 else if( mEventData->mUpdateRightSelectionPosition )
231 UpdateSelectionHandle( RIGHT_SELECTION_HANDLE );
233 if( mEventData->mScrollAfterUpdateCursorPosition )
235 ScrollToMakeCursorVisible();
236 mEventData->mScrollAfterUpdateCursorPosition = false;
239 mEventData->mDecoratorUpdated = true;
240 mEventData->mUpdateRightSelectionPosition = false;
243 mEventData->mEventQueue.clear();
245 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::ProcessInputEvents\n" );
246 return mEventData->mDecoratorUpdated;
249 void Controller::Impl::UpdateModel( OperationsMask operationsRequired )
251 // Calculate the operations to be done.
252 const OperationsMask operations = static_cast<OperationsMask>( mOperationsPending & operationsRequired );
254 Vector<Character>& utf32Characters = mLogicalModel->mText;
256 const Length numberOfCharacters = utf32Characters.Count();
258 Vector<LineBreakInfo>& lineBreakInfo = mLogicalModel->mLineBreakInfo;
259 if( GET_LINE_BREAKS & operations )
261 // Retrieves the line break info. The line break info is used to split the text in 'paragraphs' to
262 // calculate the bidirectional info for each 'paragraph'.
263 // It's also used to layout the text (where it should be a new line) or to shape the text (text in different lines
264 // is not shaped together).
265 lineBreakInfo.Resize( numberOfCharacters, TextAbstraction::LINE_NO_BREAK );
267 SetLineBreakInfo( utf32Characters,
271 Vector<WordBreakInfo>& wordBreakInfo = mLogicalModel->mWordBreakInfo;
272 if( GET_WORD_BREAKS & operations )
274 // Retrieves the word break info. The word break info is used to layout the text (where to wrap the text in lines).
275 wordBreakInfo.Resize( numberOfCharacters, TextAbstraction::WORD_NO_BREAK );
277 SetWordBreakInfo( utf32Characters,
281 const bool getScripts = GET_SCRIPTS & operations;
282 const bool validateFonts = VALIDATE_FONTS & operations;
284 Vector<ScriptRun>& scripts = mLogicalModel->mScriptRuns;
285 Vector<FontRun>& validFonts = mLogicalModel->mFontRuns;
287 if( getScripts || validateFonts )
289 // Validates the fonts assigned by the application or assigns default ones.
290 // It makes sure all the characters are going to be rendered by the correct font.
291 MultilanguageSupport multilanguageSupport = MultilanguageSupport::Get();
295 // Retrieves the scripts used in the text.
296 multilanguageSupport.SetScripts( utf32Characters,
303 if( 0u == validFonts.Count() )
305 // Copy the requested font defaults received via the property system.
306 // These may not be valid i.e. may not contain glyphs for the necessary scripts.
307 GetDefaultFonts( validFonts, numberOfCharacters );
310 // Validates the fonts. If there is a character with no assigned font it sets a default one.
311 // After this call, fonts are validated.
312 multilanguageSupport.ValidateFonts( utf32Characters,
318 Vector<Character> mirroredUtf32Characters;
319 bool textMirrored = false;
320 if( BIDI_INFO & operations )
322 // Count the number of LINE_NO_BREAK to reserve some space for the vector of paragraph's
323 // bidirectional info.
325 Length numberOfParagraphs = 0u;
327 const TextAbstraction::LineBreakInfo* lineBreakInfoBuffer = lineBreakInfo.Begin();
328 for( Length index = 0u; index < numberOfCharacters; ++index )
330 if( TextAbstraction::LINE_NO_BREAK == *( lineBreakInfoBuffer + index ) )
332 ++numberOfParagraphs;
336 Vector<BidirectionalParagraphInfoRun>& bidirectionalInfo = mLogicalModel->mBidirectionalParagraphInfo;
337 bidirectionalInfo.Reserve( numberOfParagraphs );
339 // Calculates the bidirectional info for the whole paragraph if it contains right to left scripts.
340 SetBidirectionalInfo( utf32Characters,
345 if( 0u != bidirectionalInfo.Count() )
347 // This paragraph has right to left text. Some characters may need to be mirrored.
348 // TODO: consider if the mirrored string can be stored as well.
350 textMirrored = GetMirroredText( utf32Characters, mirroredUtf32Characters );
352 // Only set the character directions if there is right to left characters.
353 Vector<CharacterDirection>& directions = mLogicalModel->mCharacterDirections;
354 directions.Resize( numberOfCharacters );
356 GetCharactersDirection( bidirectionalInfo,
361 // There is no right to left characters. Clear the directions vector.
362 mLogicalModel->mCharacterDirections.Clear();
367 Vector<GlyphInfo>& glyphs = mVisualModel->mGlyphs;
368 Vector<CharacterIndex>& glyphsToCharactersMap = mVisualModel->mGlyphsToCharacters;
369 Vector<Length>& charactersPerGlyph = mVisualModel->mCharactersPerGlyph;
370 if( SHAPE_TEXT & operations )
372 const Vector<Character>& textToShape = textMirrored ? mirroredUtf32Characters : utf32Characters;
374 ShapeText( textToShape,
379 glyphsToCharactersMap,
380 charactersPerGlyph );
382 // Create the 'number of glyphs' per character and the glyph to character conversion tables.
383 mVisualModel->CreateGlyphsPerCharacterTable( numberOfCharacters );
384 mVisualModel->CreateCharacterToGlyphTable( numberOfCharacters );
387 const Length numberOfGlyphs = glyphs.Count();
389 if( GET_GLYPH_METRICS & operations )
391 mFontClient.GetGlyphMetrics( glyphs.Begin(), numberOfGlyphs );
395 void Controller::Impl::GetDefaultFonts( Vector<FontRun>& fonts, Length numberOfCharacters )
400 fontRun.characterRun.characterIndex = 0;
401 fontRun.characterRun.numberOfCharacters = numberOfCharacters;
402 fontRun.fontId = mFontDefaults->GetFontId( mFontClient );
403 fontRun.isDefault = true;
405 fonts.PushBack( fontRun );
409 void Controller::Impl::OnKeyboardFocus( bool hasFocus )
411 if( NULL == mEventData )
413 // Nothing to do if there is no text input.
419 ChangeState( EventData::INACTIVE );
423 ChangeState( EventData::EDITING );
427 void Controller::Impl::OnCursorKeyEvent( const Event& event )
429 if( NULL == mEventData )
431 // Nothing to do if there is no text input.
435 int keyCode = event.p1.mInt;
437 if( Dali::DALI_KEY_CURSOR_LEFT == keyCode )
439 if( mEventData->mPrimaryCursorPosition > 0u )
441 mEventData->mPrimaryCursorPosition = CalculateNewCursorIndex( mEventData->mPrimaryCursorPosition - 1u );
444 else if( Dali::DALI_KEY_CURSOR_RIGHT == keyCode )
446 if( mLogicalModel->GetNumberOfCharacters() > mEventData->mPrimaryCursorPosition )
448 mEventData->mPrimaryCursorPosition = CalculateNewCursorIndex( mEventData->mPrimaryCursorPosition );
451 else if( Dali::DALI_KEY_CURSOR_UP == keyCode )
455 else if( Dali::DALI_KEY_CURSOR_DOWN == keyCode )
460 mEventData->mUpdateCursorPosition = true;
461 mEventData->mScrollAfterUpdateCursorPosition = true;
464 void Controller::Impl::OnTapEvent( const Event& event )
466 if( NULL != mEventData )
468 const unsigned int tapCount = event.p1.mUint;
472 if( ! IsShowingPlaceholderText() )
474 const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
475 const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
477 mEventData->mPrimaryCursorPosition = GetClosestCursorIndex( xPosition,
482 mEventData->mPrimaryCursorPosition = 0u;
485 mEventData->mUpdateCursorPosition = true;
486 mEventData->mScrollAfterUpdateCursorPosition = true;
488 else if( mEventData->mSelectionEnabled &&
491 RepositionSelectionHandles( event.p2.mFloat, event.p3.mFloat );
496 void Controller::Impl::OnPanEvent( const Event& event )
498 if( NULL == mEventData )
500 // Nothing to do if there is no text input.
504 int state = event.p1.mInt;
506 if( Gesture::Started == state ||
507 Gesture::Continuing == state )
509 const Vector2& actualSize = mVisualModel->GetActualSize();
510 const Vector2 currentScroll = mEventData->mScrollPosition;
512 if( mEventData->mHorizontalScrollingEnabled )
514 const float displacementX = event.p2.mFloat;
515 mEventData->mScrollPosition.x += displacementX;
517 ClampHorizontalScroll( actualSize );
520 if( mEventData->mVerticalScrollingEnabled )
522 const float displacementY = event.p3.mFloat;
523 mEventData->mScrollPosition.y += displacementY;
525 ClampVerticalScroll( actualSize );
528 if( mEventData->mDecorator )
530 mEventData->mDecorator->UpdatePositions( mEventData->mScrollPosition - currentScroll );
535 void Controller::Impl::OnHandleEvent( const Event& event )
537 if( NULL == mEventData )
539 // Nothing to do if there is no text input.
543 const unsigned int state = event.p1.mUint;
545 if( HANDLE_PRESSED == state )
547 // The event.p2 and event.p3 are in decorator coords. Need to transforms to text coords.
548 const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
549 const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
551 const CharacterIndex handleNewPosition = GetClosestCursorIndex( xPosition, yPosition );
553 if( Event::GRAB_HANDLE_EVENT == event.type )
555 ChangeState ( EventData::EDITING );
557 if( handleNewPosition != mEventData->mPrimaryCursorPosition )
559 mEventData->mPrimaryCursorPosition = handleNewPosition;
560 mEventData->mUpdateCursorPosition = true;
563 else if( Event::LEFT_SELECTION_HANDLE_EVENT == event.type )
565 if( handleNewPosition != mEventData->mLeftSelectionPosition )
567 mEventData->mLeftSelectionPosition = handleNewPosition;
568 mEventData->mUpdateLeftSelectionPosition = true;
571 else if( Event::RIGHT_SELECTION_HANDLE_EVENT == event.type )
573 if( handleNewPosition != mEventData->mRightSelectionPosition )
575 mEventData->mRightSelectionPosition = handleNewPosition;
576 mEventData->mUpdateRightSelectionPosition = true;
580 else if( ( HANDLE_RELEASED == state ) ||
581 ( HANDLE_STOP_SCROLLING == state ) )
583 if( mEventData->mGrabHandlePopupEnabled )
585 ChangeState( EventData::EDITING_WITH_POPUP );
587 if( Event::GRAB_HANDLE_EVENT == event.type )
589 mEventData->mUpdateCursorPosition = true;
591 if( HANDLE_STOP_SCROLLING == state )
593 // The event.p2 and event.p3 are in decorator coords. Need to transforms to text coords.
594 const float xPosition = event.p2.mFloat - mEventData->mScrollPosition.x - mAlignmentOffset.x;
595 const float yPosition = event.p3.mFloat - mEventData->mScrollPosition.y - mAlignmentOffset.y;
597 mEventData->mPrimaryCursorPosition = GetClosestCursorIndex( xPosition, yPosition );
599 mEventData->mScrollAfterUpdateCursorPosition = true;
602 mEventData->mDecoratorUpdated = true;
604 else if( HANDLE_SCROLLING == state )
606 const float xSpeed = event.p2.mFloat;
607 const Vector2& actualSize = mVisualModel->GetActualSize();
609 mEventData->mScrollPosition.x += xSpeed;
611 ClampHorizontalScroll( actualSize );
613 mEventData->mDecoratorUpdated = true;
617 void Controller::Impl::RepositionSelectionHandles( float visualX, float visualY )
619 if( NULL == mEventData )
621 // Nothing to do if there is no text input.
625 // TODO - Find which word was selected
627 const Vector<GlyphInfo>& glyphs = mVisualModel->mGlyphs;
628 const Vector<Vector2>::SizeType glyphCount = glyphs.Count();
630 const Vector<Vector2>& positions = mVisualModel->mGlyphPositions;
631 const Vector<Vector2>::SizeType positionCount = positions.Count();
633 // Guard against glyphs which did not fit inside the layout
634 const Vector<Vector2>::SizeType count = (positionCount < glyphCount) ? positionCount : glyphCount;
638 float primaryX = positions[0].x + mEventData->mScrollPosition.x;
639 float secondaryX = positions[count-1].x + glyphs[count-1].width + mEventData->mScrollPosition.x;
641 // TODO - multi-line selection
642 const Vector<LineRun>& lines = mVisualModel->mLines;
643 float height = lines.Count() ? lines[0].ascender + -lines[0].descender : 0.0f;
645 mEventData->mDecorator->SetPosition( LEFT_SELECTION_HANDLE, primaryX, mEventData->mScrollPosition.y, height );
646 mEventData->mDecorator->SetPosition( RIGHT_SELECTION_HANDLE, secondaryX, mEventData->mScrollPosition.y, height );
648 mEventData->mDecorator->ClearHighlights();
649 mEventData->mDecorator->AddHighlight( primaryX, mEventData->mScrollPosition.y, secondaryX, height + mEventData->mScrollPosition.y );
653 void Controller::Impl::ChangeState( EventData::State newState )
655 if( NULL == mEventData )
657 // Nothing to do if there is no text input.
661 if( mEventData->mState != newState )
663 mEventData->mState = newState;
665 if( EventData::INACTIVE == mEventData->mState )
667 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
668 mEventData->mDecorator->StopCursorBlink();
669 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
670 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
671 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
672 mEventData->mDecorator->SetPopupActive( false );
673 mEventData->mDecoratorUpdated = true;
675 else if ( EventData::SELECTING == mEventData->mState )
677 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
678 mEventData->mDecorator->StopCursorBlink();
679 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
680 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, true );
681 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, true );
682 mEventData->mDecoratorUpdated = true;
684 else if( EventData::EDITING == mEventData->mState )
686 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
687 if( mEventData->mCursorBlinkEnabled )
689 mEventData->mDecorator->StartCursorBlink();
691 // Grab handle is not shown until a tap is received whilst EDITING
692 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
693 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, false );
694 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, false );
695 mEventData->mDecoratorUpdated = true;
697 else if( EventData::EDITING_WITH_POPUP == mEventData->mState )
699 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
700 if( mEventData->mCursorBlinkEnabled )
702 mEventData->mDecorator->StartCursorBlink();
704 mEventData->mDecorator->SetHandleActive( GRAB_HANDLE, false );
705 if( mEventData->mSelectionEnabled )
707 mEventData->mDecorator->SetHandleActive( LEFT_SELECTION_HANDLE, true );
708 mEventData->mDecorator->SetHandleActive( RIGHT_SELECTION_HANDLE, true );
710 if( mEventData->mGrabHandlePopupEnabled )
712 mEventData->mDecorator->SetPopupActive( true );
714 mEventData->mDecoratorUpdated = true;
719 LineIndex Controller::Impl::GetClosestLine( float y ) const
721 float totalHeight = 0.f;
722 LineIndex lineIndex = 0u;
724 const Vector<LineRun>& lines = mVisualModel->mLines;
725 for( LineIndex endLine = lines.Count();
729 const LineRun& lineRun = lines[lineIndex];
730 totalHeight += lineRun.ascender + -lineRun.descender;
731 if( y < totalHeight )
740 CharacterIndex Controller::Impl::GetClosestCursorIndex( float visualX,
743 if( NULL == mEventData )
745 // Nothing to do if there is no text input.
749 CharacterIndex logicalIndex = 0u;
751 const Length numberOfGlyphs = mVisualModel->mGlyphs.Count();
752 const Length numberOfLines = mVisualModel->mLines.Count();
753 if( 0 == numberOfGlyphs ||
759 // Find which line is closest
760 const LineIndex lineIndex = GetClosestLine( visualY );
761 const LineRun& line = mVisualModel->mLines[lineIndex];
763 // Get the positions of the glyphs.
764 const Vector<Vector2>& positions = mVisualModel->mGlyphPositions;
765 const Vector2* const positionsBuffer = positions.Begin();
767 // Get the visual to logical conversion tables.
768 const CharacterIndex* const visualToLogicalBuffer = ( 0u != mLogicalModel->mVisualToLogicalMap.Count() ) ? mLogicalModel->mVisualToLogicalMap.Begin() : NULL;
769 const CharacterIndex* const visualToLogicalCursorBuffer = mLogicalModel->mVisualToLogicalCursorMap.Begin();
771 // Get the character to glyph conversion table.
772 const GlyphIndex* const charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
774 // Get the glyphs per character table.
775 const Length* const glyphsPerCharacterBuffer = mVisualModel->mGlyphsPerCharacter.Begin();
777 // If the vector is void, there is no right to left characters.
778 const bool hasRightToLeftCharacters = NULL != visualToLogicalBuffer;
780 const CharacterIndex startCharacter = line.characterRun.characterIndex;
781 const CharacterIndex endCharacter = line.characterRun.characterIndex + line.characterRun.numberOfCharacters;
782 DALI_ASSERT_DEBUG( endCharacter <= mLogicalModel->mText.Count() && "Invalid line info" );
784 // Whether there is a hit on a glyph.
785 bool matched = false;
787 // Traverses glyphs in visual order. To do that use the visual to logical conversion table.
788 CharacterIndex visualIndex = startCharacter;
789 for( ; !matched && ( visualIndex < endCharacter ); ++visualIndex )
791 // The character in logical order.
792 const CharacterIndex characterLogicalOrderIndex = hasRightToLeftCharacters ? *( visualToLogicalBuffer + visualIndex ) : visualIndex;
794 // The first glyph for that character in logical order.
795 const GlyphIndex glyphLogicalOrderIndex = *( charactersToGlyphBuffer + characterLogicalOrderIndex );
797 // The number of glyphs for that character
798 const Length numberOfGlyphs = *( glyphsPerCharacterBuffer + characterLogicalOrderIndex );
800 // Get the metrics for the group of glyphs.
801 GlyphMetrics glyphMetrics;
802 GetGlyphsMetrics( glyphLogicalOrderIndex,
808 const Vector2& position = *( positionsBuffer + glyphLogicalOrderIndex );
810 const float glyphX = -glyphMetrics.xBearing + position.x + 0.5f * glyphMetrics.advance;
812 if( visualX < glyphX )
819 // Return the logical position of the cursor in characters.
823 visualIndex = endCharacter;
826 return hasRightToLeftCharacters ? *( visualToLogicalCursorBuffer + visualIndex ) : visualIndex;
829 void Controller::Impl::GetCursorPosition( CharacterIndex logical,
830 CursorInfo& cursorInfo )
832 // TODO: Check for multiline with \n, etc...
834 // Check if the logical position is the first or the last one of the text.
835 const bool isFirstPosition = 0u == logical;
836 const bool isLastPosition = mLogicalModel->GetNumberOfCharacters() == logical;
838 if( isFirstPosition && isLastPosition )
840 // There is zero characters. Get the default font.
842 FontId defaultFontId = 0u;
843 if( NULL == mFontDefaults )
845 defaultFontId = mFontClient.GetFontId( EMPTY_STRING,
850 defaultFontId = mFontDefaults->GetFontId( mFontClient );
853 Text::FontMetrics fontMetrics;
854 mFontClient.GetFontMetrics( defaultFontId, fontMetrics );
856 cursorInfo.lineHeight = fontMetrics.ascender - fontMetrics.descender;
857 cursorInfo.primaryCursorHeight = cursorInfo.lineHeight;
859 cursorInfo.primaryPosition.x = 0.f;
860 cursorInfo.primaryPosition.y = 0.f;
862 // Nothing else to do.
866 // Get the previous logical index.
867 const CharacterIndex previousLogical = isFirstPosition ? 0u : logical - 1u;
869 // Decrease the logical index if it's the last one.
875 // Get the direction of the character and the previous one.
876 const CharacterDirection* const modelCharacterDirectionsBuffer = ( 0u != mLogicalModel->mCharacterDirections.Count() ) ? mLogicalModel->mCharacterDirections.Begin() : NULL;
878 CharacterDirection isCurrentRightToLeft = false;
879 CharacterDirection isPreviousRightToLeft = false;
880 if( NULL != modelCharacterDirectionsBuffer ) // If modelCharacterDirectionsBuffer is NULL, it means the whole text is left to right.
882 isCurrentRightToLeft = *( modelCharacterDirectionsBuffer + logical );
883 isPreviousRightToLeft = *( modelCharacterDirectionsBuffer + previousLogical );
886 // Get the line where the character is laid-out.
887 const LineRun* modelLines = mVisualModel->mLines.Begin();
889 const LineIndex lineIndex = mVisualModel->GetLineOfCharacter( logical );
890 const LineRun& line = *( modelLines + lineIndex );
892 // Get the paragraph's direction.
893 const CharacterDirection isRightToLeftParagraph = line.direction;
895 // Check whether there is an alternative position:
897 cursorInfo.isSecondaryCursor = ( isCurrentRightToLeft != isPreviousRightToLeft ) ||
898 ( isLastPosition && ( isRightToLeftParagraph != isCurrentRightToLeft ) );
900 // Set the line height.
901 cursorInfo.lineHeight = line.ascender + -line.descender;
903 // Convert the cursor position into the glyph position.
904 CharacterIndex characterIndex = logical;
905 if( cursorInfo.isSecondaryCursor &&
906 ( isRightToLeftParagraph != isCurrentRightToLeft ) )
908 characterIndex = previousLogical;
911 const GlyphIndex currentGlyphIndex = *( mVisualModel->mCharactersToGlyph.Begin() + characterIndex );
912 const Length numberOfGlyphs = *( mVisualModel->mGlyphsPerCharacter.Begin() + characterIndex );
913 const Length numberOfCharacters = *( mVisualModel->mCharactersPerGlyph.Begin() +currentGlyphIndex );
915 // Get the metrics for the group of glyphs.
916 GlyphMetrics glyphMetrics;
917 GetGlyphsMetrics( currentGlyphIndex,
923 float interGlyphAdvance = 0.f;
924 if( !isLastPosition &&
925 ( numberOfCharacters > 1u ) )
927 const CharacterIndex firstIndex = *( mVisualModel->mGlyphsToCharacters.Begin() + currentGlyphIndex );
928 interGlyphAdvance = static_cast<float>( characterIndex - firstIndex ) * glyphMetrics.advance / static_cast<float>( numberOfCharacters );
931 // Get the glyph position and x bearing.
932 const Vector2& currentPosition = *( mVisualModel->mGlyphPositions.Begin() + currentGlyphIndex );
934 // Set the cursor's height.
935 cursorInfo.primaryCursorHeight = glyphMetrics.fontHeight;
938 cursorInfo.primaryPosition.x = -glyphMetrics.xBearing + currentPosition.x + ( isCurrentRightToLeft ? glyphMetrics.advance : interGlyphAdvance );
939 cursorInfo.primaryPosition.y = line.ascender - glyphMetrics.ascender;
943 // The position of the cursor after the last character needs special
944 // care depending on its direction and the direction of the paragraph.
946 if( cursorInfo.isSecondaryCursor )
948 // Need to find the first character after the last character with the paragraph's direction.
949 // i.e l0 l1 l2 r0 r1 should find r0.
951 // TODO: check for more than one line!
952 characterIndex = isRightToLeftParagraph ? line.characterRun.characterIndex : line.characterRun.characterIndex + line.characterRun.numberOfCharacters - 1u;
953 characterIndex = mLogicalModel->GetLogicalCharacterIndex( characterIndex );
955 const GlyphIndex glyphIndex = *( mVisualModel->mCharactersToGlyph.Begin() + characterIndex );
956 const Length numberOfGlyphs = *( mVisualModel->mGlyphsPerCharacter.Begin() + characterIndex );
958 const Vector2& position = *( mVisualModel->mGlyphPositions.Begin() + glyphIndex );
960 // Get the metrics for the group of glyphs.
961 GlyphMetrics glyphMetrics;
962 GetGlyphsMetrics( glyphIndex,
968 cursorInfo.primaryPosition.x = -glyphMetrics.xBearing + position.x + ( isRightToLeftParagraph ? 0.f : glyphMetrics.advance );
970 cursorInfo.primaryPosition.y = line.ascender - glyphMetrics.ascender;
974 if( !isCurrentRightToLeft )
976 cursorInfo.primaryPosition.x += glyphMetrics.advance;
980 cursorInfo.primaryPosition.x -= glyphMetrics.advance;
985 // Set the alternative cursor position.
986 if( cursorInfo.isSecondaryCursor )
988 // Convert the cursor position into the glyph position.
989 const CharacterIndex previousCharacterIndex = ( ( isRightToLeftParagraph != isCurrentRightToLeft ) ? logical : previousLogical );
990 const GlyphIndex previousGlyphIndex = *( mVisualModel->mCharactersToGlyph.Begin() + previousCharacterIndex );
991 const Length numberOfGlyphs = *( mVisualModel->mGlyphsPerCharacter.Begin() + previousCharacterIndex );
993 // Get the glyph position.
994 const Vector2& previousPosition = *( mVisualModel->mGlyphPositions.Begin() + previousGlyphIndex );
996 // Get the metrics for the group of glyphs.
997 GlyphMetrics glyphMetrics;
998 GetGlyphsMetrics( previousGlyphIndex,
1004 // Set the cursor position and height.
1005 cursorInfo.secondaryPosition.x = -glyphMetrics.xBearing + previousPosition.x + ( ( ( isLastPosition && !isCurrentRightToLeft ) ||
1006 ( !isLastPosition && isCurrentRightToLeft ) ) ? glyphMetrics.advance : 0.f );
1008 cursorInfo.secondaryCursorHeight = 0.5f * glyphMetrics.fontHeight;
1010 cursorInfo.secondaryPosition.y = cursorInfo.lineHeight - cursorInfo.secondaryCursorHeight - line.descender - ( glyphMetrics.fontHeight - glyphMetrics.ascender );
1012 // Update the primary cursor height as well.
1013 cursorInfo.primaryCursorHeight *= 0.5f;
1017 CharacterIndex Controller::Impl::CalculateNewCursorIndex( CharacterIndex index ) const
1019 if( NULL == mEventData )
1021 // Nothing to do if there is no text input.
1025 CharacterIndex cursorIndex = mEventData->mPrimaryCursorPosition;
1027 const Script script = mLogicalModel->GetScript( index );
1028 const GlyphIndex* charactersToGlyphBuffer = mVisualModel->mCharactersToGlyph.Begin();
1029 const Length* charactersPerGlyphBuffer = mVisualModel->mCharactersPerGlyph.Begin();
1031 Length numberOfCharacters = 0u;
1032 if( TextAbstraction::LATIN == script )
1034 // Prevents to jump the whole Latin ligatures like fi, ff, ...
1035 numberOfCharacters = 1u;
1039 GlyphIndex glyphIndex = *( charactersToGlyphBuffer + index );
1040 numberOfCharacters = *( charactersPerGlyphBuffer + glyphIndex );
1042 while( 0u == numberOfCharacters )
1044 numberOfCharacters = *( charactersPerGlyphBuffer + glyphIndex );
1049 if( index < mEventData->mPrimaryCursorPosition )
1051 cursorIndex -= numberOfCharacters;
1055 cursorIndex += numberOfCharacters;
1061 void Controller::Impl::UpdateCursorPosition()
1063 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "-->Controller::UpdateCursorPosition %p\n", this );
1064 if( NULL == mEventData )
1066 // Nothing to do if there is no text input.
1067 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition no event data\n" );
1071 CursorInfo cursorInfo;
1072 GetCursorPosition( mEventData->mPrimaryCursorPosition,
1075 const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset;
1076 const Vector2 cursorPosition = cursorInfo.primaryPosition + offset;
1078 // Sets the cursor position.
1079 mEventData->mDecorator->SetPosition( PRIMARY_CURSOR,
1082 cursorInfo.primaryCursorHeight,
1083 cursorInfo.lineHeight );
1084 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Primary cursor position: %f,%f\n", cursorPosition.x, cursorPosition.y );
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 );
1100 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "Secondary cursor position: %f,%f\n", cursorInfo.secondaryPosition.x + offset.x, cursorInfo.secondaryPosition.y + offset.y );
1104 mEventData->mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
1106 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--Controller::UpdateCursorPosition\n" );
1109 void Controller::Impl::UpdateSelectionHandle( HandleType handleType )
1111 if( ( LEFT_SELECTION_HANDLE != handleType ) &&
1112 ( RIGHT_SELECTION_HANDLE != handleType ) )
1117 const bool leftSelectionHandle = LEFT_SELECTION_HANDLE == handleType;
1118 const CharacterIndex index = leftSelectionHandle ? mEventData->mLeftSelectionPosition : mEventData->mRightSelectionPosition;
1120 CursorInfo cursorInfo;
1121 GetCursorPosition( index,
1124 const Vector2 offset = mEventData->mScrollPosition + mAlignmentOffset;
1125 const Vector2 cursorPosition = cursorInfo.primaryPosition + offset;
1127 // Sets the grab handle position.
1128 mEventData->mDecorator->SetPosition( handleType,
1131 cursorInfo.lineHeight );
1134 void Controller::Impl::ClampHorizontalScroll( const Vector2& actualSize )
1136 // Clamp between -space & 0 (and the text alignment).
1137 if( actualSize.width > mControlSize.width )
1139 const float space = ( actualSize.width - mControlSize.width ) + mAlignmentOffset.x;
1140 mEventData->mScrollPosition.x = ( mEventData->mScrollPosition.x < -space ) ? -space : mEventData->mScrollPosition.x;
1141 mEventData->mScrollPosition.x = ( mEventData->mScrollPosition.x > -mAlignmentOffset.x ) ? -mAlignmentOffset.x : mEventData->mScrollPosition.x;
1143 mEventData->mDecoratorUpdated = true;
1147 mEventData->mScrollPosition.x = 0.f;
1151 void Controller::Impl::ClampVerticalScroll( const Vector2& actualSize )
1153 // Clamp between -space & 0 (and the text alignment).
1154 if( actualSize.height > mControlSize.height )
1156 const float space = ( actualSize.height - mControlSize.height ) + mAlignmentOffset.y;
1157 mEventData->mScrollPosition.y = ( mEventData->mScrollPosition.y < -space ) ? -space : mEventData->mScrollPosition.y;
1158 mEventData->mScrollPosition.y = ( mEventData->mScrollPosition.y > -mAlignmentOffset.y ) ? -mAlignmentOffset.y : mEventData->mScrollPosition.y;
1160 mEventData->mDecoratorUpdated = true;
1164 mEventData->mScrollPosition.y = 0.f;
1168 void Controller::Impl::ScrollToMakeCursorVisible()
1170 if( NULL == mEventData )
1172 // Nothing to do if there is no text input.
1176 const Vector2& primaryCursorPosition = mEventData->mDecorator->GetPosition( PRIMARY_CURSOR );
1179 bool updateDecorator = false;
1180 if( primaryCursorPosition.x < 0.f )
1182 offset.x = -primaryCursorPosition.x;
1183 mEventData->mScrollPosition.x += offset.x;
1184 updateDecorator = true;
1186 else if( primaryCursorPosition.x > mControlSize.width )
1188 offset.x = mControlSize.width - primaryCursorPosition.x;
1189 mEventData->mScrollPosition.x += offset.x;
1190 updateDecorator = true;
1193 if( updateDecorator && mEventData->mDecorator )
1195 mEventData->mDecorator->UpdatePositions( offset );
1198 // TODO : calculate the vertical scroll.
1201 void Controller::Impl::RequestRelayout()
1203 mControlInterface.RequestTextRelayout();
1208 } // namespace Toolkit