2 // Copyright (c) 2014 Samsung Electronics Co., Ltd.
4 // Licensed under the Flora License, Version 1.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://floralicense.org/license/
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.
18 #include "text-view-word-group-processor.h"
19 #include "text-view-word-processor.h"
20 #include "text-view-processor-helper-functions.h"
21 #include "text-processor.h"
32 namespace TextViewProcessor
39 WordGroupLayoutInfo::WordGroupLayoutInfo()
44 mNumberOfCharacters( 0 )
48 WordGroupLayoutInfo::WordGroupLayoutInfo( const WordGroupLayoutInfo& group )
49 : mSize( group.mSize ),
50 mAscender( group.mAscender ),
51 mDirection( group.mDirection ),
52 mWordsLayoutInfo( group.mWordsLayoutInfo ),
53 mNumberOfCharacters( group.mNumberOfCharacters )
57 WordGroupLayoutInfo& WordGroupLayoutInfo::operator=( const WordGroupLayoutInfo& group )
60 mAscender = group.mAscender;
61 mDirection = group.mDirection;
62 mWordsLayoutInfo = group.mWordsLayoutInfo;
63 mNumberOfCharacters = group.mNumberOfCharacters;
68 void UpdateGroupLayoutInfo( TextViewProcessor::WordGroupLayoutInfo& wordGroupLayoutInfo )
70 wordGroupLayoutInfo.mSize = Size();
72 for( WordLayoutInfoContainer::iterator it = wordGroupLayoutInfo.mWordsLayoutInfo.begin(), endIt = wordGroupLayoutInfo.mWordsLayoutInfo.end();
76 WordLayoutInfo& layoutInfo( *it );
78 UpdateSize( wordGroupLayoutInfo.mSize, layoutInfo.mSize );
82 void CreateWordGroupInfo( const MarkupProcessor::StyledTextArray& wordGroup,
83 TextViewProcessor::TextLayoutInfo& textLayoutInfo,
84 TextViewProcessor::WordGroupLayoutInfo& wordGroupLayoutInfo )
86 // Set the direction of the group.
87 wordGroupLayoutInfo.mDirection = ( TextProcessor::BeginsRightToLeftCharacter( wordGroup ) ? TextViewProcessor::RTL : TextViewProcessor::LTR );
89 // Split the group of words in words
90 std::vector<MarkupProcessor::StyledTextArray> words;
91 TextProcessor::SplitInWords( wordGroup, words );
93 // if last word has a new line separator, create a new word.
96 MarkupProcessor::StyledTextArray& word( *( words.end() - 1 ) );
99 // do nothing if the word has only one character.
100 MarkupProcessor::StyledText& styledText( *( word.end() - 1 ) );
101 if( !styledText.mText.IsEmpty() )
103 const std::size_t length = styledText.mText.GetLength();
104 if( styledText.mText[length-1].IsNewLine() )
106 // Last character of this word is a new line character.
108 // Remove line separator character from current word.
109 styledText.mText.Remove( length - 1, 1 );
111 // Create a new word with the line separator character.
112 MarkupProcessor::StyledText newLineText( Text( styledText.mText[length-1] ), styledText.mStyle );
114 MarkupProcessor::StyledTextArray newLineWord;
115 newLineWord.push_back( newLineText );
117 words.push_back( newLineWord );
123 // Reverse if right to left.
124 if( TextViewProcessor::RTL == wordGroupLayoutInfo.mDirection )
126 std::reverse( words.begin(), words.end() );
129 std::string lastCharacterFont; // Keeps the font used by the last character. It's used to set the font to a word separator.
131 // Traverse all words.
132 for( std::vector<MarkupProcessor::StyledTextArray>::const_iterator wordIt = words.begin(), wordEndIt = words.end(); wordIt != wordEndIt; ++wordIt )
134 const MarkupProcessor::StyledTextArray& word( *wordIt );
136 // Data structures for the new word.
137 WordLayoutInfo wordLayoutInfo;
139 CreateWordTextInfo( word,
142 // 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
143 // avoid 'jumps' of characters when there is a switch between one text-actor per character and one text-actor per line and/or style.
144 if( WordSeparator == wordLayoutInfo.mType )
146 // If current word is a word separator (white space) then the font of the last character is set.
147 for( CharacterLayoutInfoContainer::iterator characterIt = wordLayoutInfo.mCharactersLayoutInfo.begin(), characterEndIt = wordLayoutInfo.mCharactersLayoutInfo.end();
148 characterIt != characterEndIt;
151 CharacterLayoutInfo& characterLayout( *characterIt );
153 characterLayout.mStyledText.mStyle.SetFontName( lastCharacterFont );
158 // kepps the font of the last character.
159 if( !wordLayoutInfo.mCharactersLayoutInfo.empty() )
161 lastCharacterFont = ( *( wordLayoutInfo.mCharactersLayoutInfo.end() - 1 ) ).mStyledText.mStyle.GetFontName();
165 // Update layout info for the current group of words.
166 wordGroupLayoutInfo.mNumberOfCharacters += wordLayoutInfo.mCharactersLayoutInfo.size();
167 UpdateSize( wordGroupLayoutInfo.mSize, wordLayoutInfo.mSize );
168 wordGroupLayoutInfo.mAscender = std::max( wordGroupLayoutInfo.mAscender, wordLayoutInfo.mAscender );
170 // Add current word to the group of words.
171 wordGroupLayoutInfo.mWordsLayoutInfo.push_back( wordLayoutInfo );
173 // Update the max word width figure.
174 textLayoutInfo.mMaxWordWidth = std::max( textLayoutInfo.mMaxWordWidth, wordLayoutInfo.mSize.width );
178 void RemoveCharactersFromWordGroupInfo( TextView::RelayoutData& relayoutData,
179 const std::size_t numberOfCharacters,
182 TextViewProcessor::TextInfoIndices& textInfoIndicesBegin,
183 TextViewProcessor::TextInfoIndices& textInfoIndicesEnd,
184 TextViewProcessor::TextInfoIndices& textInfoMergeIndicesBegin,
185 TextViewProcessor::TextInfoIndices& textInfoMergeIndicesEnd,
186 TextViewProcessor::WordGroupLayoutInfo& groupLayout,
187 std::vector<TextActor>& removedTextActorsFromFirstWord,
188 std::vector<TextActor>& removedTextActorsFromLastWord )
190 const TextViewProcessor::TextLayoutInfo& textLayoutInfo = relayoutData.mTextLayoutInfo;
192 if( textInfoIndicesBegin.mWordIndex < textInfoIndicesEnd.mWordIndex )
194 // Deleted text is from different words. The two different words may be merged.
197 WordLayoutInfo& firstWordLayout( *( groupLayout.mWordsLayoutInfo.begin() + textInfoIndicesBegin.mWordIndex ) );
200 WordLayoutInfo& lastWordLayout( *( groupLayout.mWordsLayoutInfo.begin() + textInfoIndicesEnd.mWordIndex ) );
202 // whether first or last word need to be split and merged.
203 bool mergeFromBegin = false;
204 bool mergeToEnd = false;
206 if( textInfoIndicesBegin.mCharacterIndex > 0 )
208 // First word is going to be split. It could be merged with the last word.
209 mergeFromBegin = true;
210 textInfoMergeIndicesBegin.mWordIndex = textInfoIndicesBegin.mWordIndex;
212 else if( ( textInfoIndicesBegin.mCharacterIndex == 0 ) && ( textInfoIndicesBegin.mWordIndex > 0 ) )
214 // First word is going to be removed completely.
215 // Check if previous word could be merged.
218 WordLayoutInfo& previousWordLayout( *( groupLayout.mWordsLayoutInfo.begin() + textInfoIndicesBegin.mWordIndex - 1 ) );
219 if( WordSeparator != previousWordLayout.mType )
221 // Previous word is not a word separator, so could be merged.
222 mergeFromBegin = true;
223 textInfoMergeIndicesBegin.mWordIndex = textInfoIndicesBegin.mWordIndex - 1;
229 // First word (or previous one) could be merged. Check if last one could be merged as well.
231 if( textInfoIndicesEnd.mCharacterIndex + 1 < lastWordLayout.mCharactersLayoutInfo.size() )
233 // Last word is going to be split. It could be merged with the first word.
235 textInfoMergeIndicesEnd.mWordIndex = textInfoIndicesEnd.mWordIndex;
237 else if( ( textInfoIndicesEnd.mCharacterIndex + 1 == lastWordLayout.mCharactersLayoutInfo.size() ) && ( textInfoIndicesEnd.mWordIndex + 1 < groupLayout.mWordsLayoutInfo.size() ) )
239 // Last word is going to be removed completely.
240 // Check if the word after could be merged.
243 WordLayoutInfo& afterWordLayout( *( groupLayout.mWordsLayoutInfo.begin() + textInfoIndicesEnd.mWordIndex + 1 ) );
244 if( WordSeparator != afterWordLayout.mType )
246 // The word after is not a word separator, so could be merged.
248 textInfoMergeIndicesEnd.mWordIndex = textInfoIndicesEnd.mWordIndex + 1;
252 // Merge words only if both words could be merged.
253 mergeWords = mergeFromBegin && mergeToEnd;
256 if( ( textInfoIndicesEnd.mCharacterIndex + 1 == lastWordLayout.mCharactersLayoutInfo.size() ) && ( textInfoIndicesEnd.mWordIndex + 1 == groupLayout.mWordsLayoutInfo.size() ) )
258 // Last word of the line is going to be removed completely.
259 // Check if it's a line separator.
261 if( LineSeparator == lastWordLayout.mType )
263 // The line separator is going to be removed.
264 if( textInfoIndicesBegin.mLineIndex + 1 < textLayoutInfo.mLinesLayoutInfo.size() )
266 // Line need to be merged.
267 textInfoMergeIndicesBegin.mLineIndex = textInfoIndicesBegin.mLineIndex;
268 textInfoMergeIndicesEnd.mLineIndex = textInfoIndicesBegin.mLineIndex + 1;
271 ++textInfoIndicesBegin.mLineIndex; // increase both indices,
272 textInfoIndicesEnd.mLineIndex +=2; // will delete last line.
277 if( textInfoIndicesBegin.mCharacterIndex > 0 )
279 // First word needs to be split.
281 // Store text-actors before removing them.
282 CollectTextActors( removedTextActorsFromFirstWord, firstWordLayout, textInfoIndicesBegin.mCharacterIndex, firstWordLayout.mCharactersLayoutInfo.size() );
284 RemoveCharactersFromWord( textInfoIndicesBegin.mCharacterIndex,
285 firstWordLayout.mCharactersLayoutInfo.size() - textInfoIndicesBegin.mCharacterIndex,
288 ++textInfoIndicesBegin.mWordIndex; // will delete from the word after.
291 if( textInfoIndicesEnd.mCharacterIndex + 1 < lastWordLayout.mCharactersLayoutInfo.size() )
293 // Last word needs to be split.
295 // Store text-actors before removing them.
296 CollectTextActors( removedTextActorsFromLastWord, lastWordLayout, 0, textInfoIndicesEnd.mCharacterIndex + 1 );
298 RemoveCharactersFromWord( 0,
299 textInfoIndicesEnd.mCharacterIndex + 1,
304 // This word is going to be merged, so is not needed.
305 ++textInfoIndicesEnd.mWordIndex; // will delete the last word.
308 else if( textInfoIndicesEnd.mCharacterIndex + 1 == lastWordLayout.mCharactersLayoutInfo.size() )
310 // The whole last word is going to be removed.
311 ++textInfoIndicesEnd.mWordIndex; // will delete the last word.
313 if( ( WordSeparator == lastWordLayout.mType ) && mergeWords )
315 // The last word is a word separator and the word after is going to be merged so is not needed.
316 ++textInfoIndicesEnd.mWordIndex; // will delete the word after the last one.
322 // Chraracters to be removed are from the same word.
324 RemoveCharactersFromWordInfo( relayoutData,
328 textInfoIndicesBegin,
330 textInfoMergeIndicesBegin,
331 textInfoMergeIndicesEnd,
333 removedTextActorsFromFirstWord );
337 void RemoveWordsFromWordGroup( const std::size_t wordIndex,
338 const std::size_t numberOfWords,
339 WordGroupLayoutInfo& wordGroupLayoutInfo )
341 // Removes words from a group of words.
343 // * Check if words or lines can be merged after removing a word or line separator have to be done outside this method.
345 // * Note: Currently it's only used to remove a number of words from the beginning, or
346 // from wordIndex index to the end. This function doesn't merge words (if a white space is removed)
347 // TODO: merge words if required.
349 const std::size_t wordEndIndex = wordIndex + numberOfWords;
351 // Remove words from layout info.
352 wordGroupLayoutInfo.mWordsLayoutInfo.erase( wordGroupLayoutInfo.mWordsLayoutInfo.begin() + wordIndex,
353 wordGroupLayoutInfo.mWordsLayoutInfo.begin() + wordEndIndex );
355 // update layout info
356 wordGroupLayoutInfo.mSize = Size();
357 wordGroupLayoutInfo.mAscender = 0.f;
358 wordGroupLayoutInfo.mNumberOfCharacters = 0;
359 for( WordLayoutInfoContainer::const_iterator it = wordGroupLayoutInfo.mWordsLayoutInfo.begin(), endIt = wordGroupLayoutInfo.mWordsLayoutInfo.end();
363 const WordLayoutInfo& info( *it );
365 UpdateSize( wordGroupLayoutInfo.mSize, info.mSize );
366 wordGroupLayoutInfo.mAscender = std::max( wordGroupLayoutInfo.mAscender, info.mAscender );
367 wordGroupLayoutInfo.mNumberOfCharacters += info.mCharactersLayoutInfo.size();
371 void SplitWordGroup( const TextInfoIndices& indices,
372 WordGroupLayoutInfo& firstWordGroupLayoutInfo,
373 WordGroupLayoutInfo& lastWordGroupLayoutInfo )
375 // Splits a group of words in two.
376 // A word may be split in two as well.
378 // * Split the word pointed by indices.mWordIndex using the indices.mCharacterIndex index.
379 // * Add the last part of the word as first word of the last part of the group of words.
380 // * Add folliwing words to the last part of the new group of words.
381 // * Remove from the first part of the group of words all words added to the last part of the group of words.
382 // * Update layout info.
385 if( ( 0 == indices.mWordIndex ) && ( 0 == indices.mCharacterIndex ) )
387 // the whole group of words goes to the last part of the group.
388 lastWordGroupLayoutInfo = firstWordGroupLayoutInfo;
390 firstWordGroupLayoutInfo = WordGroupLayoutInfo();
395 if( !firstWordGroupLayoutInfo.mWordsLayoutInfo.empty() )
397 const std::size_t numberOfWords = firstWordGroupLayoutInfo.mWordsLayoutInfo.size();
398 if( indices.mWordIndex == numberOfWords - 1 )
400 const WordLayoutInfo& word( *( firstWordGroupLayoutInfo.mWordsLayoutInfo.end() - 1 ) );
401 if( indices.mCharacterIndex == word.mCharactersLayoutInfo.size() )
403 // the whole group of words goes to the first part.
405 // Just delete whatever there is in the last part of the group of words.
406 lastWordGroupLayoutInfo = WordGroupLayoutInfo();
413 lastWordGroupLayoutInfo = WordGroupLayoutInfo();
415 // 1) Split the word within the group of words to be split.
416 WordLayoutInfo& firstWordLayoutInfo( *( firstWordGroupLayoutInfo.mWordsLayoutInfo.begin() + indices.mWordIndex ) );
417 WordLayoutInfo lastWordLayoutInfo;
419 SplitWord( indices.mCharacterIndex,
421 lastWordLayoutInfo );
423 // 2) Add last part of the word to the new group of words.
424 if( !lastWordLayoutInfo.mCharactersLayoutInfo.empty() )
426 lastWordGroupLayoutInfo.mWordsLayoutInfo.push_back( lastWordLayoutInfo );
429 // 3) Add words from word-position + 1 to the end.
430 lastWordGroupLayoutInfo.mWordsLayoutInfo.insert( lastWordGroupLayoutInfo.mWordsLayoutInfo.end(),
431 firstWordGroupLayoutInfo.mWordsLayoutInfo.begin() + indices.mWordIndex + 1, firstWordGroupLayoutInfo.mWordsLayoutInfo.end() );
433 // 4) update layout info of the last group of words.
434 lastWordGroupLayoutInfo.mDirection = firstWordGroupLayoutInfo.mDirection;
436 for( WordLayoutInfoContainer::iterator it = lastWordGroupLayoutInfo.mWordsLayoutInfo.begin(), endIt = lastWordGroupLayoutInfo.mWordsLayoutInfo.end();
440 WordLayoutInfo& layoutInfo( *it );
442 UpdateSize( lastWordGroupLayoutInfo.mSize, layoutInfo.mSize );
443 lastWordGroupLayoutInfo.mNumberOfCharacters += layoutInfo.mCharactersLayoutInfo.size();
444 lastWordGroupLayoutInfo.mAscender = std::max( lastWordGroupLayoutInfo.mAscender, layoutInfo.mAscender );
447 // 5) Remove words added to the last part of the group of words from the first group of words.
449 // if the number of characters of the last word of the first group is zero, it should be removed.
450 const std::size_t index = ( firstWordLayoutInfo.mCharactersLayoutInfo.empty() ? indices.mWordIndex : indices.mWordIndex + 1 );
452 firstWordGroupLayoutInfo.mWordsLayoutInfo.erase( firstWordGroupLayoutInfo.mWordsLayoutInfo.begin() + index, firstWordGroupLayoutInfo.mWordsLayoutInfo.end() );
454 // 6) update layout info of the first group of words.
455 firstWordGroupLayoutInfo.mSize = Size();
456 firstWordGroupLayoutInfo.mAscender = 0.f;
457 firstWordGroupLayoutInfo.mNumberOfCharacters = 0;
458 for( WordLayoutInfoContainer::iterator it = firstWordGroupLayoutInfo.mWordsLayoutInfo.begin(), endIt = firstWordGroupLayoutInfo.mWordsLayoutInfo.end();
462 WordLayoutInfo& layoutInfo( *it );
464 UpdateSize( firstWordGroupLayoutInfo.mSize, layoutInfo.mSize );
465 firstWordGroupLayoutInfo.mNumberOfCharacters += layoutInfo.mCharactersLayoutInfo.size();
466 firstWordGroupLayoutInfo.mAscender = std::max( firstWordGroupLayoutInfo.mAscender, layoutInfo.mAscender );
470 void MergeWordGroup( WordGroupLayoutInfo& firstWordGroupLayoutInfo,
471 const WordGroupLayoutInfo& lastWordGroupLayoutInfo )
473 // Merges two given groups of words.
475 // Can't merge two groups if they have text with different directions (RTL , LTR )
476 // or if the last word of the first one is a line separator (new line character)
480 if( lastWordGroupLayoutInfo.mWordsLayoutInfo.empty() )
482 // Nothing to merge if last group is empty.
486 if( firstWordGroupLayoutInfo.mWordsLayoutInfo.empty() )
488 // If first group is empty, just copy the last group to the first one.
489 firstWordGroupLayoutInfo = lastWordGroupLayoutInfo;
494 // Check both groups have the same direction.
495 if( firstWordGroupLayoutInfo.mDirection != lastWordGroupLayoutInfo.mDirection )
497 DALI_ASSERT_ALWAYS( !"TextViewProcessor::MergeWordGroup(). ERROR: groups with different direction can't be merged." );
500 // Check first group doesn't finish with a new line character.
501 WordLayoutInfo& lastWordLayout( *( firstWordGroupLayoutInfo.mWordsLayoutInfo.end() - 1 ) );
502 if( LineSeparator == lastWordLayout.mType )
504 DALI_ASSERT_ALWAYS( !"TextViewProcessor::MergeWordGroup(). ERROR: A group of words can't be merged to another group which finishes with a new line character." );
507 // 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.
508 // Otherwise both words need to be merged first.
509 const WordLayoutInfo& firstWordLayout( *lastWordGroupLayoutInfo.mWordsLayoutInfo.begin() );
511 std::size_t index = 0;
512 if( ( WordSeparator != lastWordLayout.mType ) && ( WordSeparator != firstWordLayout.mType ) && ( LineSeparator != firstWordLayout.mType ) )
514 // 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.
515 // Words need to be merged.
517 MergeWord( lastWordLayout,
520 // After merging two words, the rest of the words need to be added.
521 ++index; // By increasing this index the word already merged won't be added again.
525 firstWordGroupLayoutInfo.mWordsLayoutInfo.insert( firstWordGroupLayoutInfo.mWordsLayoutInfo.end(),
526 lastWordGroupLayoutInfo.mWordsLayoutInfo.begin() + index, lastWordGroupLayoutInfo.mWordsLayoutInfo.end() );
527 UpdateSize( firstWordGroupLayoutInfo.mSize, lastWordGroupLayoutInfo.mSize );
528 firstWordGroupLayoutInfo.mAscender = std::max( firstWordGroupLayoutInfo.mAscender, lastWordGroupLayoutInfo.mAscender );
529 firstWordGroupLayoutInfo.mNumberOfCharacters += lastWordGroupLayoutInfo.mNumberOfCharacters;
532 void CollectTextActorsFromGroups( std::vector<TextActor>& textActors, const LineLayoutInfo& line, const std::size_t groupIndexBegin, const std::size_t groupIndexEnd )
534 for( WordGroupLayoutInfoContainer::const_iterator groupIt = line.mWordGroupsLayoutInfo.begin() + groupIndexBegin, groupEndIt = line.mWordGroupsLayoutInfo.begin() + groupIndexEnd;
535 groupIt != groupEndIt;
538 const WordGroupLayoutInfo& group( *groupIt );
540 CollectTextActorsFromWords( textActors, group, 0, group.mWordsLayoutInfo.size() );
544 } //namespace TextViewProcessor
546 } //namespace Internal
548 } //namespace Toolkit