2 * Copyright (c) 2017 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/cursor-helper-functions.h>
22 #include <dali/integration-api/debug.h>
25 #include <dali-toolkit/internal/text/glyph-metrics-helper.h>
30 #if defined(DEBUG_ENABLED)
31 Debug::Filter* gLogFilter = Debug::Filter::New(Debug::NoLogging, true, "LOG_TEXT_CONTROLS");
34 const Dali::Toolkit::Text::CharacterDirection LTR = false; ///< Left To Right direction.
38 FindWordData( const Dali::Toolkit::Text::Character* const textBuffer,
39 Dali::Toolkit::Text::Length totalNumberOfCharacters,
40 Dali::Toolkit::Text::CharacterIndex hitCharacter,
43 : textBuffer( textBuffer ),
44 totalNumberOfCharacters( totalNumberOfCharacters ),
45 hitCharacter( hitCharacter ),
47 isWhiteSpace( isWhiteSpace ),
48 isNewParagraph( isNewParagraph )
54 const Dali::Toolkit::Text::Character* const textBuffer;
55 Dali::Toolkit::Text::Length totalNumberOfCharacters;
56 Dali::Toolkit::Text::CharacterIndex hitCharacter;
57 Dali::Toolkit::Text::CharacterIndex foundIndex;
58 bool isWhiteSpace : 1u;
59 bool isNewParagraph : 1u;
62 bool IsWhiteSpaceOrNewParagraph( Dali::Toolkit::Text::Character character,
64 bool isHitWhiteSpaceOrNewParagraph )
66 bool isWhiteSpaceOrNewParagraph = false;
67 if( isHitWhiteSpaceOrNewParagraph )
71 // Whether the current character is a white space. Note a new paragraph character is a white space as well but here is not wanted.
72 isWhiteSpaceOrNewParagraph = Dali::TextAbstraction::IsWhiteSpace( character ) && !Dali::TextAbstraction::IsNewParagraph( character );
76 // Whether the current character is a new paragraph character.
77 isWhiteSpaceOrNewParagraph = Dali::TextAbstraction::IsNewParagraph( character );
82 // Whether the current character is a white space or a new paragraph character (note the new paragraph character is a white space as well).
83 isWhiteSpaceOrNewParagraph = Dali::TextAbstraction::IsWhiteSpace( character );
86 return isWhiteSpaceOrNewParagraph;
89 void FindStartOfWord( FindWordData& data )
91 const bool isHitWhiteSpaceOrNewParagraph = data.isWhiteSpace || data.isNewParagraph;
93 for( data.foundIndex = data.hitCharacter; data.foundIndex > 0; --data.foundIndex )
95 const Dali::Toolkit::Text::Character character = *( data.textBuffer + data.foundIndex - 1u );
97 const bool isWhiteSpaceOrNewParagraph = IsWhiteSpaceOrNewParagraph( character,
99 isHitWhiteSpaceOrNewParagraph );
101 if( isHitWhiteSpaceOrNewParagraph != isWhiteSpaceOrNewParagraph )
108 void FindEndOfWord( FindWordData& data )
110 const bool isHitWhiteSpaceOrNewParagraph = data.isWhiteSpace || data.isNewParagraph;
112 for( data.foundIndex = data.hitCharacter + 1u; data.foundIndex < data.totalNumberOfCharacters; ++data.foundIndex )
114 const Dali::Toolkit::Text::Character character = *( data.textBuffer + data.foundIndex );
116 const bool isWhiteSpaceOrNewParagraph = IsWhiteSpaceOrNewParagraph( character,
118 isHitWhiteSpaceOrNewParagraph );
120 if( isHitWhiteSpaceOrNewParagraph != isWhiteSpaceOrNewParagraph )
138 LineIndex GetClosestLine( VisualModelPtr visualModel,
142 float totalHeight = 0.f;
143 LineIndex lineIndex = 0;
151 const Vector<LineRun>& lines = visualModel->mLines;
153 for( Vector<LineRun>::ConstIterator it = lines.Begin(),
158 const LineRun& lineRun = *it;
160 // The line height is the addition of the line ascender and the line descender.
161 // However, the line descender has a negative value, hence the subtraction.
162 totalHeight += lineRun.ascender - lineRun.descender;
164 if( visualY < totalHeight )
176 return lineIndex - 1u;
179 float CalculateLineOffset( const Vector<LineRun>& lines,
180 LineIndex lineIndex )
184 for( Vector<LineRun>::ConstIterator it = lines.Begin(),
185 endIt = lines.Begin() + lineIndex;
189 const LineRun& lineRun = *it;
191 // The line height is the addition of the line ascender and the line descender.
192 // However, the line descender has a negative value, hence the subtraction.
193 offset += lineRun.ascender - lineRun.descender;
199 CharacterIndex GetClosestCursorIndex( VisualModelPtr visualModel,
200 LogicalModelPtr logicalModel,
204 CharacterHitTest::Mode mode,
205 bool& matchedCharacter )
207 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "GetClosestCursorIndex, closest visualX %f visualY %f\n", visualX, visualY );
209 // Whether there is a hit on a glyph.
210 matchedCharacter = false;
212 CharacterIndex logicalIndex = 0;
214 const Length totalNumberOfGlyphs = visualModel->mGlyphs.Count();
215 const Length totalNumberOfLines = visualModel->mLines.Count();
216 if( ( 0 == totalNumberOfGlyphs ) ||
217 ( 0 == totalNumberOfLines ) )
222 // Whether there is a hit on a line.
223 bool matchedLine = false;
225 // Find which line is closest.
226 const LineIndex lineIndex = Text::GetClosestLine( visualModel,
230 if( !matchedLine && ( CharacterHitTest::TAP == mode ) )
232 // Return the first or the last character if the touch point doesn't hit a line.
233 return ( visualY < 0.f ) ? 0 : logicalModel->mText.Count();
236 // Convert from text's coords to line's coords.
237 const LineRun& line = *( visualModel->mLines.Begin() + lineIndex );
239 // Transform the tap point from text's coords to line's coords.
240 visualX -= line.alignmentOffset;
242 // Get the positions of the glyphs.
243 const Vector2* const positionsBuffer = visualModel->mGlyphPositions.Begin();
245 // Get the character to glyph conversion table.
246 const GlyphIndex* const charactersToGlyphBuffer = visualModel->mCharactersToGlyph.Begin();
248 // Get the glyphs per character table.
249 const Length* const glyphsPerCharacterBuffer = visualModel->mGlyphsPerCharacter.Begin();
251 // Get the characters per glyph table.
252 const Length* const charactersPerGlyphBuffer = visualModel->mCharactersPerGlyph.Begin();
254 // Get the glyph's info buffer.
255 const GlyphInfo* const glyphInfoBuffer = visualModel->mGlyphs.Begin();
257 const CharacterIndex startCharacter = line.characterRun.characterIndex;
258 const CharacterIndex endCharacter = line.characterRun.characterIndex + line.characterRun.numberOfCharacters;
259 DALI_ASSERT_DEBUG( endCharacter <= logicalModel->mText.Count() && "Invalid line info" );
261 // Whether this line is a bidirectional line.
262 const bool bidiLineFetched = logicalModel->FetchBidirectionalLineInfo( startCharacter );
264 // The character's direction buffer.
265 const CharacterDirection* const directionsBuffer = bidiLineFetched ? logicalModel->mCharacterDirections.Begin() : NULL;
267 // Whether the touch point if before the first glyph.
268 bool isBeforeFirstGlyph = false;
270 // Traverses glyphs in visual order. To do that use the visual to logical conversion table.
271 CharacterIndex visualIndex = startCharacter;
272 Length numberOfVisualCharacters = 0;
273 for( ; visualIndex < endCharacter; ++visualIndex )
275 // The character in logical order.
276 const CharacterIndex characterLogicalOrderIndex = ( bidiLineFetched ? logicalModel->GetLogicalCharacterIndex( visualIndex ) : visualIndex );
277 const CharacterDirection direction = ( bidiLineFetched ? *( directionsBuffer + characterLogicalOrderIndex ) : LTR );
279 // The number of glyphs for that character
280 const Length numberOfGlyphs = *( glyphsPerCharacterBuffer + characterLogicalOrderIndex );
281 ++numberOfVisualCharacters;
283 if( 0 != numberOfGlyphs )
285 // Get the first character/glyph of the group of glyphs.
286 const CharacterIndex firstVisualCharacterIndex = 1u + visualIndex - numberOfVisualCharacters;
287 const CharacterIndex firstLogicalCharacterIndex = ( bidiLineFetched ? logicalModel->GetLogicalCharacterIndex( firstVisualCharacterIndex ) : firstVisualCharacterIndex );
288 const GlyphIndex firstLogicalGlyphIndex = *( charactersToGlyphBuffer + firstLogicalCharacterIndex );
290 // Get the metrics for the group of glyphs.
291 GlyphMetrics glyphMetrics;
292 GetGlyphsMetrics( firstLogicalGlyphIndex,
298 // Get the position of the first glyph.
299 const Vector2& position = *( positionsBuffer + firstLogicalGlyphIndex );
301 if( startCharacter == visualIndex )
303 const float glyphPosition = -glyphMetrics.xBearing + position.x;
305 if( visualX < glyphPosition )
307 isBeforeFirstGlyph = true;
312 // Whether the glyph can be split, like Latin ligatures fi, ff or Arabic (ل + ا).
313 Length numberOfCharacters = *( charactersPerGlyphBuffer + firstLogicalGlyphIndex );
314 if( direction != LTR )
316 // As characters are being traversed in visual order,
317 // for right to left ligatures, the character which contains the
318 // number of glyphs in the table is found first.
319 // Jump the number of characters to the next glyph is needed.
321 if( 0 == numberOfCharacters )
323 // TODO: This is a workaround to fix an issue with complex characters in the arabic
324 // script like i.e. رّ or الأَبْجَدِيَّة العَرَبِيَّة
325 // There are characters that are not shaped in one glyph but in combination with
326 // the next one generates two of them.
327 // The visual to logical conversion table have characters in different order than
328 // expected even if all of them are arabic.
330 // The workaround doesn't fix the issue completely but it prevents the application
331 // to hang in an infinite loop.
333 // Find the number of characters.
334 for( GlyphIndex index = firstLogicalGlyphIndex + 1u;
335 ( 0 == numberOfCharacters ) && ( index < totalNumberOfGlyphs );
338 numberOfCharacters = *( charactersPerGlyphBuffer + index );
341 if( 2u > numberOfCharacters )
346 --numberOfCharacters;
349 visualIndex += numberOfCharacters - 1u;
352 // Get the script of the character.
353 const Script script = logicalModel->GetScript( characterLogicalOrderIndex );
355 const bool isInterglyphIndex = ( numberOfCharacters > numberOfGlyphs ) && HasLigatureMustBreak( script );
356 const Length numberOfBlocks = isInterglyphIndex ? numberOfCharacters : 1u;
357 const float glyphAdvance = glyphMetrics.advance / static_cast<float>( numberOfBlocks );
359 CharacterIndex index = 0;
360 for( ; index < numberOfBlocks; ++index )
362 // Find the mid-point of the area containing the glyph
363 const float glyphCenter = -glyphMetrics.xBearing + position.x + ( static_cast<float>( index ) + 0.5f ) * glyphAdvance;
365 if( visualX < glyphCenter )
367 matchedCharacter = true;
372 if( matchedCharacter )
374 // If the glyph is shaped from more than one character, it matches the character of the glyph.
375 visualIndex = firstVisualCharacterIndex + index;
379 numberOfVisualCharacters = 0;
381 } // for characters in visual order.
383 // The number of characters of the whole text.
384 const Length totalNumberOfCharacters = logicalModel->mText.Count();
386 // Return the logical position of the cursor in characters.
388 if( !matchedCharacter )
390 if( isBeforeFirstGlyph )
392 // If no character is matched, then the first character (in visual order) of the line is used.
393 visualIndex = startCharacter;
397 // If no character is matched, then the last character (in visual order) of the line is used.
398 visualIndex = endCharacter;
402 // Get the paragraph direction.
403 const CharacterDirection paragraphDirection = line.direction;
405 if( totalNumberOfCharacters != visualIndex )
407 // The visual index is not at the end of the text.
409 if( LTR == paragraphDirection )
411 // The paragraph direction is left to right.
413 if( visualIndex == endCharacter )
415 // It places the cursor just before the last character in visual order.
416 // i.e. it places the cursor just before the '\n' or before the last character
417 // if there is a long line with no word breaks which is wrapped.
419 // It doesn't check if the closest line is the last one like the RTL branch below
420 // because the total number of characters is different than the visual index and
421 // the visual index is the last character of the line.
427 // The paragraph direction is right to left.
429 if( ( lineIndex != totalNumberOfLines - 1u ) && // is not the last line.
430 ( visualIndex == startCharacter ) )
432 // It places the cursor just after the first character in visual order.
433 // i.e. it places the cursor just after the '\n' or after the last character
434 // if there is a long line with no word breaks which is wrapped.
436 // If the last line doesn't end with '\n' it won't increase the visual index
437 // placing the cursor at the beginning of the line (in visual order).
444 // The visual index is at the end of text.
446 // If the text ends with a new paragraph character i.e. a '\n', an extra line with no characters is added at the end of the text.
447 // This branch checks if the closest line is the one with the last '\n'. If it is, it decrements the visual index to place
448 // the cursor just before the last '\n'.
450 if( ( lineIndex != totalNumberOfLines - 1u ) &&
451 TextAbstraction::IsNewParagraph( *( logicalModel->mText.Begin() + visualIndex - 1u ) ) )
457 logicalIndex = ( bidiLineFetched ? logicalModel->GetLogicalCursorIndex( visualIndex ) : visualIndex );
459 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "closest visualIndex %d logicalIndex %d\n", visualIndex, logicalIndex );
461 DALI_ASSERT_DEBUG( ( logicalIndex <= logicalModel->mText.Count() && logicalIndex >= 0 ) && "GetClosestCursorIndex - Out of bounds index" );
467 void GetCursorPosition( VisualModelPtr visualModel,
468 LogicalModelPtr logicalModel,
470 CharacterIndex logical,
471 CursorInfo& cursorInfo )
473 // Whether the logical cursor position is at the end of the whole text.
474 const bool isLastPosition = logicalModel->mText.Count() == logical;
476 // Get the line where the character is laid-out.
477 const CharacterIndex characterOfLine = isLastPosition ? ( logical - 1u ) : logical;
479 // Whether the cursor is in the last position and the last position is a new paragraph character.
480 const bool isLastNewParagraph = isLastPosition && TextAbstraction::IsNewParagraph( *( logicalModel->mText.Begin() + characterOfLine ) );
482 const LineRun* const modelLines = visualModel->mLines.Begin();
484 const LineIndex lineIndex = visualModel->GetLineOfCharacter( characterOfLine );
485 const LineRun& line = *( modelLines + lineIndex );
487 if( isLastNewParagraph )
489 // The cursor is in a new line with no characters. Place the cursor in that line.
490 const LineIndex newLineIndex = lineIndex + 1u;
491 const LineRun& newLine = *( modelLines + newLineIndex );
493 cursorInfo.isSecondaryCursor = false;
495 // Set the line offset and height.
496 cursorInfo.lineOffset = CalculateLineOffset( visualModel->mLines,
499 // The line height is the addition of the line ascender and the line descender.
500 // However, the line descender has a negative value, hence the subtraction.
501 cursorInfo.lineHeight = newLine.ascender - newLine.descender;
503 // Set the primary cursor's height.
504 cursorInfo.primaryCursorHeight = cursorInfo.lineHeight;
506 // Set the primary cursor's position.
507 cursorInfo.primaryPosition.x = 0.f;
508 cursorInfo.primaryPosition.y = cursorInfo.lineOffset;
510 // Transform the cursor info from line's coords to text's coords.
511 cursorInfo.primaryPosition.x += ( LTR == line.direction ) ? 0.f : visualModel->mControlSize.width;
515 // Whether this line is a bidirectional line.
516 const bool bidiLineFetched = logicalModel->FetchBidirectionalLineInfo( characterOfLine );
518 // Check if the logical position is the first or the last one of the line.
519 const bool isFirstPositionOfLine = line.characterRun.characterIndex == logical;
520 const bool isLastPositionOfLine = line.characterRun.characterIndex + line.characterRun.numberOfCharacters == logical;
522 // 'logical' is the logical 'cursor' index.
523 // Get the next and current logical 'character' index.
524 const CharacterIndex characterIndex = isFirstPositionOfLine ? logical : logical - 1u;
525 const CharacterIndex nextCharacterIndex = isLastPositionOfLine ? characterIndex : logical;
527 // The character's direction buffer.
528 const CharacterDirection* const directionsBuffer = bidiLineFetched ? logicalModel->mCharacterDirections.Begin() : NULL;
530 CharacterDirection isCurrentRightToLeft = false;
531 CharacterDirection isNextRightToLeft = false;
532 if( bidiLineFetched ) // If bidiLineFetched is false, it means the whole text is left to right.
534 isCurrentRightToLeft = *( directionsBuffer + characterIndex );
535 isNextRightToLeft = *( directionsBuffer + nextCharacterIndex );
538 // Get the paragraph's direction.
539 const CharacterDirection isRightToLeftParagraph = line.direction;
541 // Check whether there is an alternative position:
542 cursorInfo.isSecondaryCursor = ( ( !isLastPositionOfLine && ( isCurrentRightToLeft != isNextRightToLeft ) ) ||
543 ( isLastPositionOfLine && ( isRightToLeftParagraph != isCurrentRightToLeft ) ) ||
544 ( isFirstPositionOfLine && ( isRightToLeftParagraph != isCurrentRightToLeft ) ) );
546 // Set the line offset and height.
547 cursorInfo.lineOffset = CalculateLineOffset( visualModel->mLines,
550 // The line height is the addition of the line ascender and the line descender.
551 // However, the line descender has a negative value, hence the subtraction.
552 cursorInfo.lineHeight = line.ascender - line.descender;
554 // Calculate the primary cursor.
556 CharacterIndex index = characterIndex;
557 if( cursorInfo.isSecondaryCursor )
559 // If there is a secondary position, the primary cursor may be in a different place than the logical index.
561 if( isLastPositionOfLine )
563 // The position of the cursor after the last character needs special
564 // care depending on its direction and the direction of the paragraph.
566 // Need to find the first character after the last character with the paragraph's direction.
567 // i.e l0 l1 l2 r0 r1 should find r0.
569 index = isRightToLeftParagraph ? line.characterRun.characterIndex : line.characterRun.characterIndex + line.characterRun.numberOfCharacters - 1u;
570 if( bidiLineFetched )
572 index = logicalModel->GetLogicalCharacterIndex( index );
575 else if( isFirstPositionOfLine )
577 index = isRightToLeftParagraph ? line.characterRun.characterIndex + line.characterRun.numberOfCharacters - 1u : line.characterRun.characterIndex;
578 if( bidiLineFetched )
580 index = logicalModel->GetLogicalCharacterIndex( index );
585 index = ( isRightToLeftParagraph == isCurrentRightToLeft ) ? characterIndex : nextCharacterIndex;
589 const GlyphIndex* const charactersToGlyphBuffer = visualModel->mCharactersToGlyph.Begin();
590 const Length* const glyphsPerCharacterBuffer = visualModel->mGlyphsPerCharacter.Begin();
591 const Length* const charactersPerGlyphBuffer = visualModel->mCharactersPerGlyph.Begin();
592 const CharacterIndex* const glyphsToCharactersBuffer = visualModel->mGlyphsToCharacters.Begin();
593 const Vector2* const glyphPositionsBuffer = visualModel->mGlyphPositions.Begin();
594 const GlyphInfo* const glyphInfoBuffer = visualModel->mGlyphs.Begin();
596 // Convert the cursor position into the glyph position.
597 const GlyphIndex primaryGlyphIndex = *( charactersToGlyphBuffer + index );
598 const Length primaryNumberOfGlyphs = *( glyphsPerCharacterBuffer + index );
599 const Length primaryNumberOfCharacters = *( charactersPerGlyphBuffer + primaryGlyphIndex );
601 // Get the metrics for the group of glyphs.
602 GlyphMetrics glyphMetrics;
603 GetGlyphsMetrics( primaryGlyphIndex,
604 primaryNumberOfGlyphs,
609 // Whether to add the glyph's advance to the cursor position.
610 // i.e if the paragraph is left to right and the logical cursor is zero, the position is the position of the first glyph and the advance is not added,
611 // if the logical cursor is one, the position is the position of the first glyph and the advance is added.
612 // A 'truth table' was build and an online Karnaugh map tool was used to simplify the logic.
633 // Where F -> isFirstPosition
634 // L -> isLastPosition
635 // C -> isCurrentRightToLeft
636 // P -> isRightToLeftParagraph
637 // A -> Whether to add the glyph's advance.
639 const bool addGlyphAdvance = ( ( isLastPositionOfLine && !isRightToLeftParagraph ) ||
640 ( isFirstPositionOfLine && isRightToLeftParagraph ) ||
641 ( !isFirstPositionOfLine && !isLastPosition && !isCurrentRightToLeft ) );
643 float glyphAdvance = addGlyphAdvance ? glyphMetrics.advance : 0.f;
645 if( !isLastPositionOfLine &&
646 ( primaryNumberOfCharacters > 1u ) )
648 const CharacterIndex firstIndex = *( glyphsToCharactersBuffer + primaryGlyphIndex );
650 bool isCurrentRightToLeft = false;
651 if( bidiLineFetched ) // If bidiLineFetched is false, it means the whole text is left to right.
653 isCurrentRightToLeft = *( directionsBuffer + index );
656 Length numberOfGlyphAdvance = ( isFirstPositionOfLine ? 0 : 1u ) + characterIndex - firstIndex;
657 if( isCurrentRightToLeft )
659 numberOfGlyphAdvance = primaryNumberOfCharacters - numberOfGlyphAdvance;
662 glyphAdvance = static_cast<float>( numberOfGlyphAdvance ) * glyphMetrics.advance / static_cast<float>( primaryNumberOfCharacters );
665 // Get the glyph position and x bearing (in the line's coords).
666 const Vector2& primaryPosition = *( glyphPositionsBuffer + primaryGlyphIndex );
668 // Set the primary cursor's height.
669 cursorInfo.primaryCursorHeight = cursorInfo.isSecondaryCursor ? 0.5f * glyphMetrics.fontHeight : glyphMetrics.fontHeight;
671 // Set the primary cursor's position.
672 cursorInfo.primaryPosition.x = -glyphMetrics.xBearing + primaryPosition.x + glyphAdvance;
673 cursorInfo.primaryPosition.y = cursorInfo.lineOffset + line.ascender - glyphMetrics.ascender;
675 // Transform the cursor info from line's coords to text's coords.
676 cursorInfo.primaryPosition.x += line.alignmentOffset;
678 // Calculate the secondary cursor.
679 if( cursorInfo.isSecondaryCursor )
681 // Set the secondary cursor's height.
682 cursorInfo.secondaryCursorHeight = 0.5f * glyphMetrics.fontHeight;
684 CharacterIndex index = characterIndex;
685 if( !isLastPositionOfLine )
687 index = ( isRightToLeftParagraph == isCurrentRightToLeft ) ? nextCharacterIndex : characterIndex;
690 const GlyphIndex secondaryGlyphIndex = *( charactersToGlyphBuffer + index );
691 const Length secondaryNumberOfGlyphs = *( glyphsPerCharacterBuffer + index );
693 const Vector2& secondaryPosition = *( glyphPositionsBuffer + secondaryGlyphIndex );
695 GetGlyphsMetrics( secondaryGlyphIndex,
696 secondaryNumberOfGlyphs,
701 // Set the secondary cursor's position.
714 // Where F -> isFirstPosition
715 // C -> isCurrentRightToLeft
716 // P -> isRightToLeftParagraph
717 // A -> Whether to add the glyph's advance.
719 const bool addGlyphAdvance = ( ( !isFirstPositionOfLine && !isCurrentRightToLeft ) ||
720 ( isFirstPositionOfLine && !isRightToLeftParagraph ) );
722 cursorInfo.secondaryPosition.x = -glyphMetrics.xBearing + secondaryPosition.x + ( addGlyphAdvance ? glyphMetrics.advance : 0.f );
723 cursorInfo.secondaryPosition.y = cursorInfo.lineOffset + cursorInfo.lineHeight - cursorInfo.secondaryCursorHeight;
725 // Transform the cursor info from line's coords to text's coords.
726 cursorInfo.secondaryPosition.x += line.alignmentOffset;
731 bool FindSelectionIndices( VisualModelPtr visualModel,
732 LogicalModelPtr logicalModel,
736 CharacterIndex& startIndex,
737 CharacterIndex& endIndex,
738 CharacterIndex& noTextHitIndex )
742 |-------------------------------------------------------|------------------------------------------|
743 | On a word | The word |
744 | On a single white space between words | The word before or after the white space |
745 | On one of the multiple contiguous white spaces | The white spaces |
746 | On a single white space which is in the position zero | The white space and the next word |
747 | On a new paragraph character | The word or group of white spaces before |
748 |-------------------------------------------------------|------------------------------------------|
750 const Length totalNumberOfCharacters = logicalModel->mText.Count();
755 if( 0 == totalNumberOfCharacters )
757 // Nothing to do if the model is empty.
761 bool matchedCharacter = false;
762 CharacterIndex hitCharacter = Text::GetClosestCursorIndex( visualModel,
767 CharacterHitTest::TAP,
770 if( !matchedCharacter )
772 noTextHitIndex = hitCharacter;
775 DALI_ASSERT_DEBUG( ( hitCharacter <= totalNumberOfCharacters ) && "GetClosestCursorIndex returned out of bounds index" );
777 if( hitCharacter >= totalNumberOfCharacters )
779 // Closest hit character is the last character.
780 if( hitCharacter == totalNumberOfCharacters )
782 hitCharacter--; //Hit character index set to last character in logical model
786 // hitCharacter is out of bounds
791 const Character* const textBuffer = logicalModel->mText.Begin();
793 startIndex = hitCharacter;
794 endIndex = hitCharacter;
796 // Whether the hit character is a new paragraph character.
797 const bool isHitCharacterNewParagraph = TextAbstraction::IsNewParagraph( *( textBuffer + hitCharacter ) );
799 // Whether the hit character is a white space. Note a new paragraph character is a white space as well but here is not wanted.
800 const bool isHitCharacterWhiteSpace = TextAbstraction::IsWhiteSpace( *( textBuffer + hitCharacter ) ) && !isHitCharacterNewParagraph;
802 FindWordData data( textBuffer,
803 totalNumberOfCharacters,
805 isHitCharacterWhiteSpace,
806 isHitCharacterNewParagraph );
808 if( isHitCharacterNewParagraph )
810 // Find the first character before the hit one which is not a new paragraph character.
812 if( hitCharacter > 0 )
814 endIndex = hitCharacter - 1u;
815 for( ; endIndex > 0; --endIndex )
817 const Dali::Toolkit::Text::Character character = *( data.textBuffer + endIndex );
819 if( !Dali::TextAbstraction::IsNewParagraph( character ) )
826 data.hitCharacter = endIndex;
827 data.isNewParagraph = false;
828 data.isWhiteSpace = TextAbstraction::IsWhiteSpace( *( textBuffer + data.hitCharacter ) );
831 // Find the start of the word.
832 FindStartOfWord( data );
833 startIndex = data.foundIndex;
835 // Find the end of the word.
836 FindEndOfWord( data );
837 endIndex = data.foundIndex;
839 if( 1u == ( endIndex - startIndex ) )
841 if( isHitCharacterWhiteSpace )
843 // Select the word before or after the white space
845 if( 0 == hitCharacter )
847 data.isWhiteSpace = false;
848 FindEndOfWord( data );
849 endIndex = data.foundIndex;
851 else if( hitCharacter > 0 )
853 // Find the start of the word.
854 data.hitCharacter = hitCharacter - 1u;
855 data.isWhiteSpace = false;
856 FindStartOfWord( data );
857 startIndex = data.foundIndex;
864 return matchedCharacter;
869 } // namespace Toolkit