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 "text-view-word-group-processor.h"
20 #include "text-view-word-processor.h"
21 #include "text-view-processor-helper-functions.h"
22 #include "text-processor.h"
33 namespace TextViewProcessor
40 WordGroupLayoutInfo::WordGroupLayoutInfo()
45 mNumberOfCharacters( 0 )
49 WordGroupLayoutInfo::WordGroupLayoutInfo( const WordGroupLayoutInfo& group )
50 : mSize( group.mSize ),
51 mAscender( group.mAscender ),
52 mDirection( group.mDirection ),
53 mWordsLayoutInfo( group.mWordsLayoutInfo ),
54 mNumberOfCharacters( group.mNumberOfCharacters )
58 WordGroupLayoutInfo& WordGroupLayoutInfo::operator=( const WordGroupLayoutInfo& group )
61 mAscender = group.mAscender;
62 mDirection = group.mDirection;
63 mWordsLayoutInfo = group.mWordsLayoutInfo;
64 mNumberOfCharacters = group.mNumberOfCharacters;
69 void UpdateGroupLayoutInfo( TextViewProcessor::WordGroupLayoutInfo& wordGroupLayoutInfo )
71 wordGroupLayoutInfo.mSize = Size();
73 for( WordLayoutInfoContainer::iterator it = wordGroupLayoutInfo.mWordsLayoutInfo.begin(), endIt = wordGroupLayoutInfo.mWordsLayoutInfo.end();
77 WordLayoutInfo& layoutInfo( *it );
79 UpdateSize( wordGroupLayoutInfo.mSize, layoutInfo.mSize );
83 void CreateWordGroupInfo( const MarkupProcessor::StyledTextArray& wordGroup,
84 TextViewProcessor::TextLayoutInfo& textLayoutInfo,
85 TextViewProcessor::WordGroupLayoutInfo& wordGroupLayoutInfo )
87 // Set the direction of the group.
88 wordGroupLayoutInfo.mDirection = ( TextProcessor::BeginsRightToLeftCharacter( wordGroup ) ? TextViewProcessor::RTL : TextViewProcessor::LTR );
90 // Split the group of words in words
91 std::vector<MarkupProcessor::StyledTextArray> words;
92 TextProcessor::SplitInWords( wordGroup, words );
94 // if last word has a new line separator, create a new word.
97 MarkupProcessor::StyledTextArray& word( *( words.end() - 1 ) );
100 // do nothing if the word has only one character.
101 MarkupProcessor::StyledText& styledText( *( word.end() - 1 ) );
102 if( !styledText.mText.IsEmpty() )
104 const std::size_t length = styledText.mText.GetLength();
105 if( styledText.mText[length-1].IsNewLine() )
107 // Last character of this word is a new line character.
109 // Remove line separator character from current word.
110 styledText.mText.Remove( length - 1, 1 );
112 // Create a new word with the line separator character.
113 MarkupProcessor::StyledText newLineText( Text( styledText.mText[length-1] ), styledText.mStyle );
115 MarkupProcessor::StyledTextArray newLineWord;
116 newLineWord.push_back( newLineText );
118 words.push_back( newLineWord );
124 // Reverse if right to left.
125 if( TextViewProcessor::RTL == wordGroupLayoutInfo.mDirection )
127 std::reverse( words.begin(), words.end() );
130 std::string lastCharacterFont; // Keeps the font used by the last character. It's used to set the font to a word separator.
132 // Traverse all words.
133 for( std::vector<MarkupProcessor::StyledTextArray>::const_iterator wordIt = words.begin(), wordEndIt = words.end(); wordIt != wordEndIt; ++wordIt )
135 const MarkupProcessor::StyledTextArray& word( *wordIt );
137 // Data structures for the new word.
138 WordLayoutInfo wordLayoutInfo;
140 CreateWordTextInfo( word,
143 // 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
144 // avoid 'jumps' of characters when there is a switch between one text-actor per character and one text-actor per line and/or style.
145 if( WordSeparator == wordLayoutInfo.mType )
147 // If current word is a word separator (white space) then the font of the last character is set.
148 for( CharacterLayoutInfoContainer::iterator characterIt = wordLayoutInfo.mCharactersLayoutInfo.begin(), characterEndIt = wordLayoutInfo.mCharactersLayoutInfo.end();
149 characterIt != characterEndIt;
152 CharacterLayoutInfo& characterLayout( *characterIt );
154 characterLayout.mStyledText.mStyle.SetFontName( lastCharacterFont );
159 // kepps the font of the last character.
160 if( !wordLayoutInfo.mCharactersLayoutInfo.empty() )
162 lastCharacterFont = ( *( wordLayoutInfo.mCharactersLayoutInfo.end() - 1 ) ).mStyledText.mStyle.GetFontName();
166 // Update layout info for the current group of words.
167 wordGroupLayoutInfo.mNumberOfCharacters += wordLayoutInfo.mCharactersLayoutInfo.size();
168 UpdateSize( wordGroupLayoutInfo.mSize, wordLayoutInfo.mSize );
169 wordGroupLayoutInfo.mAscender = std::max( wordGroupLayoutInfo.mAscender, wordLayoutInfo.mAscender );
171 // Add current word to the group of words.
172 wordGroupLayoutInfo.mWordsLayoutInfo.push_back( wordLayoutInfo );
174 // Update the max word width figure.
175 textLayoutInfo.mMaxWordWidth = std::max( textLayoutInfo.mMaxWordWidth, wordLayoutInfo.mSize.width );
179 void RemoveCharactersFromWordGroupInfo( TextView::RelayoutData& relayoutData,
180 const std::size_t numberOfCharacters,
183 TextViewProcessor::TextInfoIndices& textInfoIndicesBegin,
184 TextViewProcessor::TextInfoIndices& textInfoIndicesEnd,
185 TextViewProcessor::TextInfoIndices& textInfoMergeIndicesBegin,
186 TextViewProcessor::TextInfoIndices& textInfoMergeIndicesEnd,
187 TextViewProcessor::WordGroupLayoutInfo& groupLayout,
188 std::vector<TextActor>& removedTextActorsFromFirstWord,
189 std::vector<TextActor>& removedTextActorsFromLastWord )
191 const TextViewProcessor::TextLayoutInfo& textLayoutInfo = relayoutData.mTextLayoutInfo;
193 if( textInfoIndicesBegin.mWordIndex < textInfoIndicesEnd.mWordIndex )
195 // Deleted text is from different words. The two different words may be merged.
198 WordLayoutInfo& firstWordLayout( *( groupLayout.mWordsLayoutInfo.begin() + textInfoIndicesBegin.mWordIndex ) );
201 WordLayoutInfo& lastWordLayout( *( groupLayout.mWordsLayoutInfo.begin() + textInfoIndicesEnd.mWordIndex ) );
203 // whether first or last word need to be split and merged.
204 bool mergeFromBegin = false;
205 bool mergeToEnd = false;
207 if( textInfoIndicesBegin.mCharacterIndex > 0 )
209 // First word is going to be split. It could be merged with the last word.
210 mergeFromBegin = true;
211 textInfoMergeIndicesBegin.mWordIndex = textInfoIndicesBegin.mWordIndex;
213 else if( ( textInfoIndicesBegin.mCharacterIndex == 0 ) && ( textInfoIndicesBegin.mWordIndex > 0 ) )
215 // First word is going to be removed completely.
216 // Check if previous word could be merged.
219 WordLayoutInfo& previousWordLayout( *( groupLayout.mWordsLayoutInfo.begin() + textInfoIndicesBegin.mWordIndex - 1 ) );
220 if( WordSeparator != previousWordLayout.mType )
222 // Previous word is not a word separator, so could be merged.
223 mergeFromBegin = true;
224 textInfoMergeIndicesBegin.mWordIndex = textInfoIndicesBegin.mWordIndex - 1;
230 // First word (or previous one) could be merged. Check if last one could be merged as well.
232 if( textInfoIndicesEnd.mCharacterIndex + 1 < lastWordLayout.mCharactersLayoutInfo.size() )
234 // Last word is going to be split. It could be merged with the first word.
236 textInfoMergeIndicesEnd.mWordIndex = textInfoIndicesEnd.mWordIndex;
238 else if( ( textInfoIndicesEnd.mCharacterIndex + 1 == lastWordLayout.mCharactersLayoutInfo.size() ) && ( textInfoIndicesEnd.mWordIndex + 1 < groupLayout.mWordsLayoutInfo.size() ) )
240 // Last word is going to be removed completely.
241 // Check if the word after could be merged.
244 WordLayoutInfo& afterWordLayout( *( groupLayout.mWordsLayoutInfo.begin() + textInfoIndicesEnd.mWordIndex + 1 ) );
245 if( WordSeparator != afterWordLayout.mType )
247 // The word after is not a word separator, so could be merged.
249 textInfoMergeIndicesEnd.mWordIndex = textInfoIndicesEnd.mWordIndex + 1;
253 // Merge words only if both words could be merged.
254 mergeWords = mergeFromBegin && mergeToEnd;
257 if( ( textInfoIndicesEnd.mCharacterIndex + 1 == lastWordLayout.mCharactersLayoutInfo.size() ) && ( textInfoIndicesEnd.mWordIndex + 1 == groupLayout.mWordsLayoutInfo.size() ) )
259 // Last word of the line is going to be removed completely.
260 // Check if it's a line separator.
262 if( LineSeparator == lastWordLayout.mType )
264 // The line separator is going to be removed.
265 if( textInfoIndicesBegin.mLineIndex + 1 < textLayoutInfo.mLinesLayoutInfo.size() )
267 // Line need to be merged.
268 textInfoMergeIndicesBegin.mLineIndex = textInfoIndicesBegin.mLineIndex;
269 textInfoMergeIndicesEnd.mLineIndex = textInfoIndicesBegin.mLineIndex + 1;
272 ++textInfoIndicesBegin.mLineIndex; // increase both indices,
273 textInfoIndicesEnd.mLineIndex +=2; // will delete last line.
278 if( textInfoIndicesBegin.mCharacterIndex > 0 )
280 // First word needs to be split.
282 // Store text-actors before removing them.
283 CollectTextActors( removedTextActorsFromFirstWord, firstWordLayout, textInfoIndicesBegin.mCharacterIndex, firstWordLayout.mCharactersLayoutInfo.size() );
285 RemoveCharactersFromWord( textInfoIndicesBegin.mCharacterIndex,
286 firstWordLayout.mCharactersLayoutInfo.size() - textInfoIndicesBegin.mCharacterIndex,
289 ++textInfoIndicesBegin.mWordIndex; // will delete from the word after.
292 if( textInfoIndicesEnd.mCharacterIndex + 1 < lastWordLayout.mCharactersLayoutInfo.size() )
294 // Last word needs to be split.
296 // Store text-actors before removing them.
297 CollectTextActors( removedTextActorsFromLastWord, lastWordLayout, 0, textInfoIndicesEnd.mCharacterIndex + 1 );
299 RemoveCharactersFromWord( 0,
300 textInfoIndicesEnd.mCharacterIndex + 1,
305 // This word is going to be merged, so is not needed.
306 ++textInfoIndicesEnd.mWordIndex; // will delete the last word.
309 else if( textInfoIndicesEnd.mCharacterIndex + 1 == lastWordLayout.mCharactersLayoutInfo.size() )
311 // The whole last word is going to be removed.
312 ++textInfoIndicesEnd.mWordIndex; // will delete the last word.
314 if( ( WordSeparator == lastWordLayout.mType ) && mergeWords )
316 // The last word is a word separator and the word after is going to be merged so is not needed.
317 ++textInfoIndicesEnd.mWordIndex; // will delete the word after the last one.
323 // Chraracters to be removed are from the same word.
325 RemoveCharactersFromWordInfo( relayoutData,
329 textInfoIndicesBegin,
331 textInfoMergeIndicesBegin,
332 textInfoMergeIndicesEnd,
334 removedTextActorsFromFirstWord );
338 void RemoveWordsFromWordGroup( const std::size_t wordIndex,
339 const std::size_t numberOfWords,
340 WordGroupLayoutInfo& wordGroupLayoutInfo )
342 // Removes words from a group of words.
344 // * Check if words or lines can be merged after removing a word or line separator have to be done outside this method.
346 // * Note: Currently it's only used to remove a number of words from the beginning, or
347 // from wordIndex index to the end. This function doesn't merge words (if a white space is removed)
348 // TODO: merge words if required.
350 const std::size_t wordEndIndex = wordIndex + numberOfWords;
352 // Remove words from layout info.
353 wordGroupLayoutInfo.mWordsLayoutInfo.erase( wordGroupLayoutInfo.mWordsLayoutInfo.begin() + wordIndex,
354 wordGroupLayoutInfo.mWordsLayoutInfo.begin() + wordEndIndex );
356 // update layout info
357 wordGroupLayoutInfo.mSize = Size();
358 wordGroupLayoutInfo.mAscender = 0.f;
359 wordGroupLayoutInfo.mNumberOfCharacters = 0;
360 for( WordLayoutInfoContainer::const_iterator it = wordGroupLayoutInfo.mWordsLayoutInfo.begin(), endIt = wordGroupLayoutInfo.mWordsLayoutInfo.end();
364 const WordLayoutInfo& info( *it );
366 UpdateSize( wordGroupLayoutInfo.mSize, info.mSize );
367 wordGroupLayoutInfo.mAscender = std::max( wordGroupLayoutInfo.mAscender, info.mAscender );
368 wordGroupLayoutInfo.mNumberOfCharacters += info.mCharactersLayoutInfo.size();
372 void SplitWordGroup( const TextInfoIndices& indices,
373 WordGroupLayoutInfo& firstWordGroupLayoutInfo,
374 WordGroupLayoutInfo& lastWordGroupLayoutInfo )
376 // Splits a group of words in two.
377 // A word may be split in two as well.
379 // * Split the word pointed by indices.mWordIndex using the indices.mCharacterIndex index.
380 // * Add the last part of the word as first word of the last part of the group of words.
381 // * Add folliwing words to the last part of the new group of words.
382 // * Remove from the first part of the group of words all words added to the last part of the group of words.
383 // * Update layout info.
386 if( ( 0 == indices.mWordIndex ) && ( 0 == indices.mCharacterIndex ) )
388 // the whole group of words goes to the last part of the group.
389 lastWordGroupLayoutInfo = firstWordGroupLayoutInfo;
391 firstWordGroupLayoutInfo = WordGroupLayoutInfo();
396 if( !firstWordGroupLayoutInfo.mWordsLayoutInfo.empty() )
398 const std::size_t numberOfWords = firstWordGroupLayoutInfo.mWordsLayoutInfo.size();
399 if( indices.mWordIndex == numberOfWords - 1 )
401 const WordLayoutInfo& word( *( firstWordGroupLayoutInfo.mWordsLayoutInfo.end() - 1 ) );
402 if( indices.mCharacterIndex == word.mCharactersLayoutInfo.size() )
404 // the whole group of words goes to the first part.
406 // Just delete whatever there is in the last part of the group of words.
407 lastWordGroupLayoutInfo = WordGroupLayoutInfo();
414 lastWordGroupLayoutInfo = WordGroupLayoutInfo();
416 // 1) Split the word within the group of words to be split.
417 WordLayoutInfo& firstWordLayoutInfo( *( firstWordGroupLayoutInfo.mWordsLayoutInfo.begin() + indices.mWordIndex ) );
418 WordLayoutInfo lastWordLayoutInfo;
420 SplitWord( indices.mCharacterIndex,
422 lastWordLayoutInfo );
424 // 2) Add last part of the word to the new group of words.
425 if( !lastWordLayoutInfo.mCharactersLayoutInfo.empty() )
427 lastWordGroupLayoutInfo.mWordsLayoutInfo.push_back( lastWordLayoutInfo );
430 // 3) Add words from word-position + 1 to the end.
431 lastWordGroupLayoutInfo.mWordsLayoutInfo.insert( lastWordGroupLayoutInfo.mWordsLayoutInfo.end(),
432 firstWordGroupLayoutInfo.mWordsLayoutInfo.begin() + indices.mWordIndex + 1, firstWordGroupLayoutInfo.mWordsLayoutInfo.end() );
434 // 4) update layout info of the last group of words.
435 lastWordGroupLayoutInfo.mDirection = firstWordGroupLayoutInfo.mDirection;
437 for( WordLayoutInfoContainer::iterator it = lastWordGroupLayoutInfo.mWordsLayoutInfo.begin(), endIt = lastWordGroupLayoutInfo.mWordsLayoutInfo.end();
441 WordLayoutInfo& layoutInfo( *it );
443 UpdateSize( lastWordGroupLayoutInfo.mSize, layoutInfo.mSize );
444 lastWordGroupLayoutInfo.mNumberOfCharacters += layoutInfo.mCharactersLayoutInfo.size();
445 lastWordGroupLayoutInfo.mAscender = std::max( lastWordGroupLayoutInfo.mAscender, layoutInfo.mAscender );
448 // 5) Remove words added to the last part of the group of words from the first group of words.
450 // if the number of characters of the last word of the first group is zero, it should be removed.
451 const std::size_t index = ( firstWordLayoutInfo.mCharactersLayoutInfo.empty() ? indices.mWordIndex : indices.mWordIndex + 1 );
453 firstWordGroupLayoutInfo.mWordsLayoutInfo.erase( firstWordGroupLayoutInfo.mWordsLayoutInfo.begin() + index, firstWordGroupLayoutInfo.mWordsLayoutInfo.end() );
455 // 6) update layout info of the first group of words.
456 firstWordGroupLayoutInfo.mSize = Size();
457 firstWordGroupLayoutInfo.mAscender = 0.f;
458 firstWordGroupLayoutInfo.mNumberOfCharacters = 0;
459 for( WordLayoutInfoContainer::iterator it = firstWordGroupLayoutInfo.mWordsLayoutInfo.begin(), endIt = firstWordGroupLayoutInfo.mWordsLayoutInfo.end();
463 WordLayoutInfo& layoutInfo( *it );
465 UpdateSize( firstWordGroupLayoutInfo.mSize, layoutInfo.mSize );
466 firstWordGroupLayoutInfo.mNumberOfCharacters += layoutInfo.mCharactersLayoutInfo.size();
467 firstWordGroupLayoutInfo.mAscender = std::max( firstWordGroupLayoutInfo.mAscender, layoutInfo.mAscender );
471 void MergeWordGroup( WordGroupLayoutInfo& firstWordGroupLayoutInfo,
472 const WordGroupLayoutInfo& lastWordGroupLayoutInfo )
474 // Merges two given groups of words.
476 // Can't merge two groups if they have text with different directions (RTL , LTR )
477 // or if the last word of the first one is a line separator (new line character)
481 if( lastWordGroupLayoutInfo.mWordsLayoutInfo.empty() )
483 // Nothing to merge if last group is empty.
487 if( firstWordGroupLayoutInfo.mWordsLayoutInfo.empty() )
489 // If first group is empty, just copy the last group to the first one.
490 firstWordGroupLayoutInfo = lastWordGroupLayoutInfo;
495 // Check both groups have the same direction.
496 if( firstWordGroupLayoutInfo.mDirection != lastWordGroupLayoutInfo.mDirection )
498 DALI_ASSERT_ALWAYS( !"TextViewProcessor::MergeWordGroup(). ERROR: groups with different direction can't be merged." );
501 // Check first group doesn't finish with a new line character.
502 WordLayoutInfo& lastWordLayout( *( firstWordGroupLayoutInfo.mWordsLayoutInfo.end() - 1 ) );
503 if( LineSeparator == lastWordLayout.mType )
505 DALI_ASSERT_ALWAYS( !"TextViewProcessor::MergeWordGroup(). ERROR: A group of words can't be merged to another group which finishes with a new line character." );
508 // 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.
509 // Otherwise both words need to be merged first.
510 const WordLayoutInfo& firstWordLayout( *lastWordGroupLayoutInfo.mWordsLayoutInfo.begin() );
512 std::size_t index = 0;
513 if( ( WordSeparator != lastWordLayout.mType ) && ( WordSeparator != firstWordLayout.mType ) && ( LineSeparator != firstWordLayout.mType ) )
515 // 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.
516 // Words need to be merged.
518 MergeWord( lastWordLayout,
521 // After merging two words, the rest of the words need to be added.
522 ++index; // By increasing this index the word already merged won't be added again.
526 firstWordGroupLayoutInfo.mWordsLayoutInfo.insert( firstWordGroupLayoutInfo.mWordsLayoutInfo.end(),
527 lastWordGroupLayoutInfo.mWordsLayoutInfo.begin() + index, lastWordGroupLayoutInfo.mWordsLayoutInfo.end() );
528 UpdateSize( firstWordGroupLayoutInfo.mSize, lastWordGroupLayoutInfo.mSize );
529 firstWordGroupLayoutInfo.mAscender = std::max( firstWordGroupLayoutInfo.mAscender, lastWordGroupLayoutInfo.mAscender );
530 firstWordGroupLayoutInfo.mNumberOfCharacters += lastWordGroupLayoutInfo.mNumberOfCharacters;
533 void CollectTextActorsFromGroups( std::vector<TextActor>& textActors, const LineLayoutInfo& line, const std::size_t groupIndexBegin, const std::size_t groupIndexEnd )
535 for( WordGroupLayoutInfoContainer::const_iterator groupIt = line.mWordGroupsLayoutInfo.begin() + groupIndexBegin, groupEndIt = line.mWordGroupsLayoutInfo.begin() + groupIndexEnd;
536 groupIt != groupEndIt;
539 const WordGroupLayoutInfo& group( *groupIt );
541 CollectTextActorsFromWords( textActors, group, 0, group.mWordsLayoutInfo.size() );
545 } //namespace TextViewProcessor
547 } //namespace Internal
549 } //namespace Toolkit