2 * Copyright (c) 2014 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/controls/text-view/text-view-word-group-processor.h>
22 #include <dali-toolkit/internal/controls/text-view/text-view-word-processor.h>
23 #include <dali-toolkit/internal/controls/text-view/text-view-processor-helper-functions.h>
24 #include <dali-toolkit/internal/controls/text-view/text-processor.h>
35 namespace TextViewProcessor
42 WordGroupLayoutInfo::WordGroupLayoutInfo()
47 mNumberOfCharacters( 0 )
51 WordGroupLayoutInfo::WordGroupLayoutInfo( const WordGroupLayoutInfo& group )
52 : mSize( group.mSize ),
53 mAscender( group.mAscender ),
54 mDirection( group.mDirection ),
55 mWordsLayoutInfo( group.mWordsLayoutInfo ),
56 mNumberOfCharacters( group.mNumberOfCharacters )
60 WordGroupLayoutInfo& WordGroupLayoutInfo::operator=( const WordGroupLayoutInfo& group )
63 mAscender = group.mAscender;
64 mDirection = group.mDirection;
65 mWordsLayoutInfo = group.mWordsLayoutInfo;
66 mNumberOfCharacters = group.mNumberOfCharacters;
71 void UpdateGroupLayoutInfo( TextViewProcessor::WordGroupLayoutInfo& wordGroupLayoutInfo )
73 wordGroupLayoutInfo.mSize = Size();
75 for( WordLayoutInfoContainer::iterator it = wordGroupLayoutInfo.mWordsLayoutInfo.begin(), endIt = wordGroupLayoutInfo.mWordsLayoutInfo.end();
79 WordLayoutInfo& layoutInfo( *it );
81 UpdateSize( wordGroupLayoutInfo.mSize, layoutInfo.mSize );
85 void CreateWordGroupInfo( const MarkupProcessor::StyledTextArray& wordGroup,
86 TextViewProcessor::TextLayoutInfo& textLayoutInfo,
87 TextViewProcessor::WordGroupLayoutInfo& wordGroupLayoutInfo )
89 // Set the direction of the group.
90 wordGroupLayoutInfo.mDirection = ( TextProcessor::BeginsRightToLeftCharacter( wordGroup ) ? TextViewProcessor::RTL : TextViewProcessor::LTR );
92 // Split the group of words in words
93 std::vector<MarkupProcessor::StyledTextArray> words;
94 TextProcessor::SplitInWords( wordGroup, words );
96 // if last word has a new line separator, create a new word.
99 MarkupProcessor::StyledTextArray& word( *( words.end() - 1 ) );
100 if( word.size() > 1 )
102 // do nothing if the word has only one character.
103 MarkupProcessor::StyledText& styledText( *( word.end() - 1 ) );
104 if( !styledText.mText.IsEmpty() )
106 const std::size_t length = styledText.mText.GetLength();
107 if( styledText.mText[length-1].IsNewLine() )
109 // Last character of this word is a new line character.
111 // Remove line separator character from current word.
112 styledText.mText.Remove( length - 1, 1 );
114 // Create a new word with the line separator character.
115 MarkupProcessor::StyledText newLineText( Text( styledText.mText[length-1] ), styledText.mStyle );
117 MarkupProcessor::StyledTextArray newLineWord;
118 newLineWord.push_back( newLineText );
120 words.push_back( newLineWord );
126 // Reverse if right to left.
127 if( TextViewProcessor::RTL == wordGroupLayoutInfo.mDirection )
129 std::reverse( words.begin(), words.end() );
132 std::string lastCharacterFont; // Keeps the font used by the last character. It's used to set the font to a word separator.
134 // Traverse all words.
135 for( std::vector<MarkupProcessor::StyledTextArray>::const_iterator wordIt = words.begin(), wordEndIt = words.end(); wordIt != wordEndIt; ++wordIt )
137 const MarkupProcessor::StyledTextArray& word( *wordIt );
139 // Data structures for the new word.
140 WordLayoutInfo wordLayoutInfo;
142 CreateWordTextInfo( word,
145 // White space's size could be different depending on the type of font. It's important to use the same font than the previous character to
146 // avoid 'jumps' of characters when there is a switch between one text-actor per character and one text-actor per line and/or style.
147 if( WordSeparator == wordLayoutInfo.mType )
149 // If current word is a word separator (white space) then the font of the last character is set.
150 for( CharacterLayoutInfoContainer::iterator characterIt = wordLayoutInfo.mCharactersLayoutInfo.begin(), characterEndIt = wordLayoutInfo.mCharactersLayoutInfo.end();
151 characterIt != characterEndIt;
154 CharacterLayoutInfo& characterLayout( *characterIt );
156 characterLayout.mStyledText.mStyle.SetFontName( lastCharacterFont );
161 // kepps the font of the last character.
162 if( !wordLayoutInfo.mCharactersLayoutInfo.empty() )
164 lastCharacterFont = ( *( wordLayoutInfo.mCharactersLayoutInfo.end() - 1 ) ).mStyledText.mStyle.GetFontName();
168 // Update layout info for the current group of words.
169 wordGroupLayoutInfo.mNumberOfCharacters += wordLayoutInfo.mCharactersLayoutInfo.size();
170 UpdateSize( wordGroupLayoutInfo.mSize, wordLayoutInfo.mSize );
171 wordGroupLayoutInfo.mAscender = std::max( wordGroupLayoutInfo.mAscender, wordLayoutInfo.mAscender );
173 // Add current word to the group of words.
174 wordGroupLayoutInfo.mWordsLayoutInfo.push_back( wordLayoutInfo );
176 // Update the max word width figure.
177 textLayoutInfo.mMaxWordWidth = std::max( textLayoutInfo.mMaxWordWidth, wordLayoutInfo.mSize.width );
181 void RemoveCharactersFromWordGroupInfo( TextView::RelayoutData& relayoutData,
182 const std::size_t numberOfCharacters,
185 TextViewProcessor::TextInfoIndices& textInfoIndicesBegin,
186 TextViewProcessor::TextInfoIndices& textInfoIndicesEnd,
187 TextViewProcessor::TextInfoIndices& textInfoMergeIndicesBegin,
188 TextViewProcessor::TextInfoIndices& textInfoMergeIndicesEnd,
189 TextViewProcessor::WordGroupLayoutInfo& groupLayout,
190 std::vector<TextActor>& removedTextActorsFromFirstWord,
191 std::vector<TextActor>& removedTextActorsFromLastWord )
193 const TextViewProcessor::TextLayoutInfo& textLayoutInfo = relayoutData.mTextLayoutInfo;
195 if( textInfoIndicesBegin.mWordIndex < textInfoIndicesEnd.mWordIndex )
197 // Deleted text is from different words. The two different words may be merged.
200 WordLayoutInfo& firstWordLayout( *( groupLayout.mWordsLayoutInfo.begin() + textInfoIndicesBegin.mWordIndex ) );
203 WordLayoutInfo& lastWordLayout( *( groupLayout.mWordsLayoutInfo.begin() + textInfoIndicesEnd.mWordIndex ) );
205 // whether first or last word need to be split and merged.
206 bool mergeFromBegin = false;
207 bool mergeToEnd = false;
209 if( textInfoIndicesBegin.mCharacterIndex > 0 )
211 // First word is going to be split. It could be merged with the last word.
212 mergeFromBegin = true;
213 textInfoMergeIndicesBegin.mWordIndex = textInfoIndicesBegin.mWordIndex;
215 else if( ( textInfoIndicesBegin.mCharacterIndex == 0 ) && ( textInfoIndicesBegin.mWordIndex > 0 ) )
217 // First word is going to be removed completely.
218 // Check if previous word could be merged.
221 WordLayoutInfo& previousWordLayout( *( groupLayout.mWordsLayoutInfo.begin() + textInfoIndicesBegin.mWordIndex - 1 ) );
222 if( WordSeparator != previousWordLayout.mType )
224 // Previous word is not a word separator, so could be merged.
225 mergeFromBegin = true;
226 textInfoMergeIndicesBegin.mWordIndex = textInfoIndicesBegin.mWordIndex - 1;
232 // First word (or previous one) could be merged. Check if last one could be merged as well.
234 if( textInfoIndicesEnd.mCharacterIndex + 1 < lastWordLayout.mCharactersLayoutInfo.size() )
236 // Last word is going to be split. It could be merged with the first word.
238 textInfoMergeIndicesEnd.mWordIndex = textInfoIndicesEnd.mWordIndex;
240 else if( ( textInfoIndicesEnd.mCharacterIndex + 1 == lastWordLayout.mCharactersLayoutInfo.size() ) && ( textInfoIndicesEnd.mWordIndex + 1 < groupLayout.mWordsLayoutInfo.size() ) )
242 // Last word is going to be removed completely.
243 // Check if the word after could be merged.
246 WordLayoutInfo& afterWordLayout( *( groupLayout.mWordsLayoutInfo.begin() + textInfoIndicesEnd.mWordIndex + 1 ) );
247 if( WordSeparator != afterWordLayout.mType )
249 // The word after is not a word separator, so could be merged.
251 textInfoMergeIndicesEnd.mWordIndex = textInfoIndicesEnd.mWordIndex + 1;
255 // Merge words only if both words could be merged.
256 mergeWords = mergeFromBegin && mergeToEnd;
259 if( ( textInfoIndicesEnd.mCharacterIndex + 1 == lastWordLayout.mCharactersLayoutInfo.size() ) && ( textInfoIndicesEnd.mWordIndex + 1 == groupLayout.mWordsLayoutInfo.size() ) )
261 // Last word of the line is going to be removed completely.
262 // Check if it's a line separator.
264 if( LineSeparator == lastWordLayout.mType )
266 // The line separator is going to be removed.
267 if( textInfoIndicesBegin.mLineIndex + 1 < textLayoutInfo.mLinesLayoutInfo.size() )
269 // Line need to be merged.
270 textInfoMergeIndicesBegin.mLineIndex = textInfoIndicesBegin.mLineIndex;
271 textInfoMergeIndicesEnd.mLineIndex = textInfoIndicesBegin.mLineIndex + 1;
274 ++textInfoIndicesBegin.mLineIndex; // increase both indices,
275 textInfoIndicesEnd.mLineIndex +=2; // will delete last line.
280 if( textInfoIndicesBegin.mCharacterIndex > 0 )
282 // First word needs to be split.
284 // Store text-actors before removing them.
285 CollectTextActors( removedTextActorsFromFirstWord, firstWordLayout, textInfoIndicesBegin.mCharacterIndex, firstWordLayout.mCharactersLayoutInfo.size() );
287 RemoveCharactersFromWord( textInfoIndicesBegin.mCharacterIndex,
288 firstWordLayout.mCharactersLayoutInfo.size() - textInfoIndicesBegin.mCharacterIndex,
291 ++textInfoIndicesBegin.mWordIndex; // will delete from the word after.
294 if( textInfoIndicesEnd.mCharacterIndex + 1 < lastWordLayout.mCharactersLayoutInfo.size() )
296 // Last word needs to be split.
298 // Store text-actors before removing them.
299 CollectTextActors( removedTextActorsFromLastWord, lastWordLayout, 0, textInfoIndicesEnd.mCharacterIndex + 1 );
301 RemoveCharactersFromWord( 0,
302 textInfoIndicesEnd.mCharacterIndex + 1,
307 // This word is going to be merged, so is not needed.
308 ++textInfoIndicesEnd.mWordIndex; // will delete the last word.
311 else if( textInfoIndicesEnd.mCharacterIndex + 1 == lastWordLayout.mCharactersLayoutInfo.size() )
313 // The whole last word is going to be removed.
314 ++textInfoIndicesEnd.mWordIndex; // will delete the last word.
316 if( ( WordSeparator == lastWordLayout.mType ) && mergeWords )
318 // The last word is a word separator and the word after is going to be merged so is not needed.
319 ++textInfoIndicesEnd.mWordIndex; // will delete the word after the last one.
325 // Chraracters to be removed are from the same word.
327 RemoveCharactersFromWordInfo( relayoutData,
331 textInfoIndicesBegin,
333 textInfoMergeIndicesBegin,
334 textInfoMergeIndicesEnd,
336 removedTextActorsFromFirstWord );
340 void RemoveWordsFromWordGroup( const std::size_t wordIndex,
341 const std::size_t numberOfWords,
342 WordGroupLayoutInfo& wordGroupLayoutInfo )
344 // Removes words from a group of words.
346 // * Check if words or lines can be merged after removing a word or line separator have to be done outside this method.
348 // * Note: Currently it's only used to remove a number of words from the beginning, or
349 // from wordIndex index to the end. This function doesn't merge words (if a white space is removed)
350 // TODO: merge words if required.
352 const std::size_t wordEndIndex = wordIndex + numberOfWords;
354 // Remove words from layout info.
355 wordGroupLayoutInfo.mWordsLayoutInfo.erase( wordGroupLayoutInfo.mWordsLayoutInfo.begin() + wordIndex,
356 wordGroupLayoutInfo.mWordsLayoutInfo.begin() + wordEndIndex );
358 // update layout info
359 wordGroupLayoutInfo.mSize = Size();
360 wordGroupLayoutInfo.mAscender = 0.f;
361 wordGroupLayoutInfo.mNumberOfCharacters = 0;
362 for( WordLayoutInfoContainer::const_iterator it = wordGroupLayoutInfo.mWordsLayoutInfo.begin(), endIt = wordGroupLayoutInfo.mWordsLayoutInfo.end();
366 const WordLayoutInfo& info( *it );
368 UpdateSize( wordGroupLayoutInfo.mSize, info.mSize );
369 wordGroupLayoutInfo.mAscender = std::max( wordGroupLayoutInfo.mAscender, info.mAscender );
370 wordGroupLayoutInfo.mNumberOfCharacters += info.mCharactersLayoutInfo.size();
374 void SplitWordGroup( const TextInfoIndices& indices,
375 WordGroupLayoutInfo& firstWordGroupLayoutInfo,
376 WordGroupLayoutInfo& lastWordGroupLayoutInfo )
378 // Splits a group of words in two.
379 // A word may be split in two as well.
381 // * Split the word pointed by indices.mWordIndex using the indices.mCharacterIndex index.
382 // * Add the last part of the word as first word of the last part of the group of words.
383 // * Add folliwing words to the last part of the new group of words.
384 // * Remove from the first part of the group of words all words added to the last part of the group of words.
385 // * Update layout info.
388 if( ( 0 == indices.mWordIndex ) && ( 0 == indices.mCharacterIndex ) )
390 // the whole group of words goes to the last part of the group.
391 lastWordGroupLayoutInfo = firstWordGroupLayoutInfo;
393 firstWordGroupLayoutInfo = WordGroupLayoutInfo();
398 if( !firstWordGroupLayoutInfo.mWordsLayoutInfo.empty() )
400 const std::size_t numberOfWords = firstWordGroupLayoutInfo.mWordsLayoutInfo.size();
401 if( indices.mWordIndex == numberOfWords - 1 )
403 const WordLayoutInfo& word( *( firstWordGroupLayoutInfo.mWordsLayoutInfo.end() - 1 ) );
404 if( indices.mCharacterIndex == word.mCharactersLayoutInfo.size() )
406 // the whole group of words goes to the first part.
408 // Just delete whatever there is in the last part of the group of words.
409 lastWordGroupLayoutInfo = WordGroupLayoutInfo();
416 lastWordGroupLayoutInfo = WordGroupLayoutInfo();
418 // 1) Split the word within the group of words to be split.
419 WordLayoutInfo& firstWordLayoutInfo( *( firstWordGroupLayoutInfo.mWordsLayoutInfo.begin() + indices.mWordIndex ) );
420 WordLayoutInfo lastWordLayoutInfo;
422 SplitWord( indices.mCharacterIndex,
424 lastWordLayoutInfo );
426 // 2) Add last part of the word to the new group of words.
427 if( !lastWordLayoutInfo.mCharactersLayoutInfo.empty() )
429 lastWordGroupLayoutInfo.mWordsLayoutInfo.push_back( lastWordLayoutInfo );
432 // 3) Add words from word-position + 1 to the end.
433 lastWordGroupLayoutInfo.mWordsLayoutInfo.insert( lastWordGroupLayoutInfo.mWordsLayoutInfo.end(),
434 firstWordGroupLayoutInfo.mWordsLayoutInfo.begin() + indices.mWordIndex + 1, firstWordGroupLayoutInfo.mWordsLayoutInfo.end() );
436 // 4) update layout info of the last group of words.
437 lastWordGroupLayoutInfo.mDirection = firstWordGroupLayoutInfo.mDirection;
439 for( WordLayoutInfoContainer::iterator it = lastWordGroupLayoutInfo.mWordsLayoutInfo.begin(), endIt = lastWordGroupLayoutInfo.mWordsLayoutInfo.end();
443 WordLayoutInfo& layoutInfo( *it );
445 UpdateSize( lastWordGroupLayoutInfo.mSize, layoutInfo.mSize );
446 lastWordGroupLayoutInfo.mNumberOfCharacters += layoutInfo.mCharactersLayoutInfo.size();
447 lastWordGroupLayoutInfo.mAscender = std::max( lastWordGroupLayoutInfo.mAscender, layoutInfo.mAscender );
450 // 5) Remove words added to the last part of the group of words from the first group of words.
452 // if the number of characters of the last word of the first group is zero, it should be removed.
453 const std::size_t index = ( firstWordLayoutInfo.mCharactersLayoutInfo.empty() ? indices.mWordIndex : indices.mWordIndex + 1 );
455 firstWordGroupLayoutInfo.mWordsLayoutInfo.erase( firstWordGroupLayoutInfo.mWordsLayoutInfo.begin() + index, firstWordGroupLayoutInfo.mWordsLayoutInfo.end() );
457 // 6) update layout info of the first group of words.
458 firstWordGroupLayoutInfo.mSize = Size();
459 firstWordGroupLayoutInfo.mAscender = 0.f;
460 firstWordGroupLayoutInfo.mNumberOfCharacters = 0;
461 for( WordLayoutInfoContainer::iterator it = firstWordGroupLayoutInfo.mWordsLayoutInfo.begin(), endIt = firstWordGroupLayoutInfo.mWordsLayoutInfo.end();
465 WordLayoutInfo& layoutInfo( *it );
467 UpdateSize( firstWordGroupLayoutInfo.mSize, layoutInfo.mSize );
468 firstWordGroupLayoutInfo.mNumberOfCharacters += layoutInfo.mCharactersLayoutInfo.size();
469 firstWordGroupLayoutInfo.mAscender = std::max( firstWordGroupLayoutInfo.mAscender, layoutInfo.mAscender );
473 void MergeWordGroup( WordGroupLayoutInfo& firstWordGroupLayoutInfo,
474 const WordGroupLayoutInfo& lastWordGroupLayoutInfo )
476 // Merges two given groups of words.
478 // Can't merge two groups if they have text with different directions (RTL , LTR )
479 // or if the last word of the first one is a line separator (new line character)
483 if( lastWordGroupLayoutInfo.mWordsLayoutInfo.empty() )
485 // Nothing to merge if last group is empty.
489 if( firstWordGroupLayoutInfo.mWordsLayoutInfo.empty() )
491 // If first group is empty, just copy the last group to the first one.
492 firstWordGroupLayoutInfo = lastWordGroupLayoutInfo;
497 // Check both groups have the same direction.
498 if( firstWordGroupLayoutInfo.mDirection != lastWordGroupLayoutInfo.mDirection )
500 DALI_ASSERT_ALWAYS( !"TextViewProcessor::MergeWordGroup(). ERROR: groups with different direction can't be merged." );
503 // Check first group doesn't finish with a new line character.
504 WordLayoutInfo& lastWordLayout( *( firstWordGroupLayoutInfo.mWordsLayoutInfo.end() - 1 ) );
505 if( LineSeparator == lastWordLayout.mType )
507 DALI_ASSERT_ALWAYS( !"TextViewProcessor::MergeWordGroup(). ERROR: A group of words can't be merged to another group which finishes with a new line character." );
510 // If the las word of the first group or the first word of the last group is a white space, both groups can be concatenated.
511 // Otherwise both words need to be merged first.
512 const WordLayoutInfo& firstWordLayout( *lastWordGroupLayoutInfo.mWordsLayoutInfo.begin() );
514 std::size_t index = 0;
515 if( ( WordSeparator != lastWordLayout.mType ) && ( WordSeparator != firstWordLayout.mType ) && ( LineSeparator != firstWordLayout.mType ) )
517 // Last word of the first group is not a word separator and first word of the last group is not a word or line separator.
518 // Words need to be merged.
520 MergeWord( lastWordLayout,
523 // After merging two words, the rest of the words need to be added.
524 ++index; // By increasing this index the word already merged won't be added again.
528 firstWordGroupLayoutInfo.mWordsLayoutInfo.insert( firstWordGroupLayoutInfo.mWordsLayoutInfo.end(),
529 lastWordGroupLayoutInfo.mWordsLayoutInfo.begin() + index, lastWordGroupLayoutInfo.mWordsLayoutInfo.end() );
530 UpdateSize( firstWordGroupLayoutInfo.mSize, lastWordGroupLayoutInfo.mSize );
531 firstWordGroupLayoutInfo.mAscender = std::max( firstWordGroupLayoutInfo.mAscender, lastWordGroupLayoutInfo.mAscender );
532 firstWordGroupLayoutInfo.mNumberOfCharacters += lastWordGroupLayoutInfo.mNumberOfCharacters;
535 void CollectTextActorsFromGroups( std::vector<TextActor>& textActors, const LineLayoutInfo& line, const std::size_t groupIndexBegin, const std::size_t groupIndexEnd )
537 for( WordGroupLayoutInfoContainer::const_iterator groupIt = line.mWordGroupsLayoutInfo.begin() + groupIndexBegin, groupEndIt = line.mWordGroupsLayoutInfo.begin() + groupIndexEnd;
538 groupIt != groupEndIt;
541 const WordGroupLayoutInfo& group( *groupIt );
543 CollectTextActorsFromWords( textActors, group, 0, group.mWordsLayoutInfo.size() );
547 } //namespace TextViewProcessor
549 } //namespace Internal
551 } //namespace Toolkit