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-line-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 LineLayoutInfo::LineLayoutInfo()
45 mLineHeightOffset( 0.f ),
47 mNumberOfCharacters( 0u )
51 LineLayoutInfo::LineLayoutInfo( const LineLayoutInfo& line )
52 : mSize( line.mSize ),
53 mAscender( line.mAscender ),
54 mLineHeightOffset( line.mLineHeightOffset ),
55 mWordsLayoutInfo( line.mWordsLayoutInfo ),
56 mNumberOfCharacters( line.mNumberOfCharacters )
60 LineLayoutInfo& LineLayoutInfo::operator=( const LineLayoutInfo& line )
63 mAscender = line.mAscender;
64 mLineHeightOffset = line.mLineHeightOffset;
65 mWordsLayoutInfo = line.mWordsLayoutInfo;
66 mNumberOfCharacters = line.mNumberOfCharacters;
71 void CreateLineInfo( const MarkupProcessor::StyledTextArray& line,
72 TextView::RelayoutData& relayoutData,
73 LineLayoutInfo& lineLayoutInfo )
75 // Split the line in words.
76 // TODO: Proper RTL support.
77 MarkupProcessor::StyledTextArray convertedLine;
78 if( TextProcessor::ContainsRightToLeftCharacter( line ) )
80 // If the text is bidirectional, the characters will be converted and reordered
81 // as specified by the Unicode Bidirectional Algorithm.
83 // Reorders the line and converts arabic glyphs (if any).
84 TextProcessor::ConvertBidirectionalText( line,
86 relayoutData.mCharacterLogicalToVisualMap,
87 relayoutData.mCharacterVisualToLogicalMap);
91 // No bidirectional text to process.
94 // Create trivial bidirectional map tables.
95 std::size_t index = 0u;
96 for( MarkupProcessor::StyledTextArray::const_iterator it = convertedLine.begin(), endIt = convertedLine.end(); it != endIt; ++it )
98 const MarkupProcessor::StyledText& styledText( *it );
100 for( std::size_t i = 0u, length = styledText.mText.GetLength(); i < length; ++i )
102 relayoutData.mCharacterLogicalToVisualMap.push_back( relayoutData.mTextLayoutInfo.mNumberOfCharacters + index );
103 relayoutData.mCharacterVisualToLogicalMap.push_back( relayoutData.mTextLayoutInfo.mNumberOfCharacters + index );
109 // Split the line in words
110 std::vector<MarkupProcessor::StyledTextArray> words;
111 TextProcessor::SplitInWords( convertedLine, words );
113 // if last word has a new line separator, create a new word.
116 MarkupProcessor::StyledTextArray& word( *( words.end() - 1u ) );
117 if( word.size() > 1u )
119 // do nothing if the word has only one character.
120 MarkupProcessor::StyledText& styledText( *( word.end() - 1u ) );
121 if( !styledText.mText.IsEmpty() )
123 const std::size_t length = styledText.mText.GetLength();
124 if( styledText.mText[length-1u].IsNewLine() )
126 // Last character of this word is a new line character.
128 // Remove line separator character from current word.
129 styledText.mText.Remove( length - 1u, 1u );
131 // Create a new word with the line separator character.
132 MarkupProcessor::StyledText newLineText( Text( styledText.mText[length-1u] ), styledText.mStyle );
134 MarkupProcessor::StyledTextArray newLineWord;
135 newLineWord.push_back( newLineText );
137 words.push_back( newLineWord );
143 std::string lastCharacterFont; // Keeps the font used by the last character. It's used to set the font to a word separator.
145 // Traverse all words.
146 for( std::vector<MarkupProcessor::StyledTextArray>::const_iterator wordIt = words.begin(), wordEndIt = words.end(); wordIt != wordEndIt; ++wordIt )
148 const MarkupProcessor::StyledTextArray& word( *wordIt );
150 // Data structures for the new word.
151 WordLayoutInfo wordLayoutInfo;
153 CreateWordTextInfo( word,
156 // 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
157 // avoid 'jumps' of characters when there is a switch between one text-actor per character and one text-actor per line and/or style.
158 if( WordSeparator == wordLayoutInfo.mType )
160 // If current word is a word separator (white space) then the font of the last character is set.
161 for( CharacterLayoutInfoContainer::iterator characterIt = wordLayoutInfo.mCharactersLayoutInfo.begin(), characterEndIt = wordLayoutInfo.mCharactersLayoutInfo.end();
162 characterIt != characterEndIt;
165 CharacterLayoutInfo& characterLayout( *characterIt );
167 characterLayout.mStyledText.mStyle.SetFontName( lastCharacterFont );
172 // kepps the font of the last character.
173 if( !wordLayoutInfo.mCharactersLayoutInfo.empty() )
175 lastCharacterFont = ( *( wordLayoutInfo.mCharactersLayoutInfo.end() - 1u ) ).mStyledText.mStyle.GetFontName();
179 // Update the max word width figure.
180 relayoutData.mTextLayoutInfo.mMaxWordWidth = std::max( relayoutData.mTextLayoutInfo.mMaxWordWidth, wordLayoutInfo.mSize.width );
182 // Update layout info for the current line.
183 lineLayoutInfo.mAscender = std::max( lineLayoutInfo.mAscender, wordLayoutInfo.mAscender );
184 lineLayoutInfo.mNumberOfCharacters += wordLayoutInfo.mCharactersLayoutInfo.size();
185 UpdateSize( lineLayoutInfo.mSize, wordLayoutInfo.mSize );
187 // Add the word to the current line.
188 lineLayoutInfo.mWordsLayoutInfo.push_back( wordLayoutInfo );
192 void UpdateLayoutInfo( LineLayoutInfo& lineLayoutInfo, const float lineHeightOffset )
194 // Update layout info.
195 lineLayoutInfo.mSize = Size::ZERO;
196 lineLayoutInfo.mAscender = 0.f;
197 lineLayoutInfo.mNumberOfCharacters = 0u;
198 for( WordLayoutInfoContainer::iterator it = lineLayoutInfo.mWordsLayoutInfo.begin(), endIt = lineLayoutInfo.mWordsLayoutInfo.end();
202 WordLayoutInfo& word( *it );
204 UpdateSize( lineLayoutInfo.mSize, word.mSize );
205 lineLayoutInfo.mAscender = std::max( lineLayoutInfo.mAscender, word.mAscender );
206 lineLayoutInfo.mNumberOfCharacters += word.mCharactersLayoutInfo.size();
209 lineLayoutInfo.mSize.height += lineHeightOffset;
210 lineLayoutInfo.mLineHeightOffset = lineHeightOffset;
213 void RemoveWordsFromLine( std::size_t wordIndex,
214 std::size_t numberOfWords,
215 float lineHeightOffset,
216 LineLayoutInfo& lineLayout )
218 // Removes words from a line.
220 // * Check if words or lines can be merged after removing a number of words or a line separator needs to be done outside this method.
222 // Remove words from layout info.
223 lineLayout.mWordsLayoutInfo.erase( lineLayout.mWordsLayoutInfo.begin() + wordIndex,
224 lineLayout.mWordsLayoutInfo.begin() + ( wordIndex + numberOfWords ) );
226 UpdateLayoutInfo( lineLayout, lineHeightOffset );
229 void RemoveCharactersFromLineInfo( TextView::RelayoutData& relayoutData,
230 const std::size_t numberOfCharacters,
233 TextInfoIndices& textInfoIndicesBegin,
234 TextInfoIndices& textInfoIndicesEnd,
235 TextInfoIndices& textInfoMergeIndicesBegin,
236 TextInfoIndices& textInfoMergeIndicesEnd,
237 LineLayoutInfo& lineLayout,
238 std::vector<TextActor>& removedTextActorsFromFirstWord,
239 std::vector<TextActor>& removedTextActorsFromLastWord )
241 const TextLayoutInfo& textLayoutInfo = relayoutData.mTextLayoutInfo;
243 if( textInfoIndicesBegin.mWordIndex < textInfoIndicesEnd.mWordIndex )
245 // Deleted text is from different words. The two different words may be merged.
248 WordLayoutInfo& firstWordLayout( *( lineLayout.mWordsLayoutInfo.begin() + textInfoIndicesBegin.mWordIndex ) );
251 WordLayoutInfo& lastWordLayout( *( lineLayout.mWordsLayoutInfo.begin() + textInfoIndicesEnd.mWordIndex ) );
253 // whether first or last word need to be split and merged.
254 bool mergeFromBegin = false;
255 bool mergeToEnd = false;
257 if( textInfoIndicesBegin.mCharacterIndex > 0u )
259 // First word is going to be split. It could be merged with the last word.
260 mergeFromBegin = true;
261 textInfoMergeIndicesBegin.mWordIndex = textInfoIndicesBegin.mWordIndex;
263 else if( ( textInfoIndicesBegin.mCharacterIndex == 0u ) && ( textInfoIndicesBegin.mWordIndex > 0u ) )
265 // First word is going to be removed completely.
266 // Check if previous word could be merged.
269 WordLayoutInfo& previousWordLayout( *( lineLayout.mWordsLayoutInfo.begin() + textInfoIndicesBegin.mWordIndex - 1u ) );
270 if( WordSeparator != previousWordLayout.mType )
272 // Previous word is not a word separator, so could be merged.
273 mergeFromBegin = true;
274 textInfoMergeIndicesBegin.mWordIndex = textInfoIndicesBegin.mWordIndex - 1u;
280 // First word (or previous one) could be merged. Check if last one could be merged as well.
282 if( textInfoIndicesEnd.mCharacterIndex + 1u < lastWordLayout.mCharactersLayoutInfo.size() )
284 // Last word is going to be split. It could be merged with the first word.
286 textInfoMergeIndicesEnd.mWordIndex = textInfoIndicesEnd.mWordIndex;
288 else if( ( textInfoIndicesEnd.mCharacterIndex + 1u == lastWordLayout.mCharactersLayoutInfo.size() ) && ( textInfoIndicesEnd.mWordIndex + 1u < lineLayout.mWordsLayoutInfo.size() ) )
290 // Last word is going to be removed completely.
291 // Check if the word after could be merged.
294 WordLayoutInfo& afterWordLayout( *( lineLayout.mWordsLayoutInfo.begin() + textInfoIndicesEnd.mWordIndex + 1u ) );
295 if( WordSeparator != afterWordLayout.mType )
297 // The word after is not a word separator, so could be merged.
299 textInfoMergeIndicesEnd.mWordIndex = textInfoIndicesEnd.mWordIndex + 1u;
303 // Merge words only if both words could be merged.
304 mergeWords = mergeFromBegin && mergeToEnd;
307 if( ( textInfoIndicesEnd.mCharacterIndex + 1u == lastWordLayout.mCharactersLayoutInfo.size() ) && ( textInfoIndicesEnd.mWordIndex + 1u == lineLayout.mWordsLayoutInfo.size() ) )
309 // Last word of the line is going to be removed completely.
310 // Check if it's a line separator.
312 if( LineSeparator == lastWordLayout.mType )
314 // The line separator is going to be removed.
315 if( textInfoIndicesBegin.mLineIndex + 1u < textLayoutInfo.mLinesLayoutInfo.size() )
317 // Line need to be merged.
318 textInfoMergeIndicesBegin.mLineIndex = textInfoIndicesBegin.mLineIndex;
319 textInfoMergeIndicesEnd.mLineIndex = textInfoIndicesBegin.mLineIndex + 1u;
322 ++textInfoIndicesBegin.mLineIndex; // increase both indices,
323 textInfoIndicesEnd.mLineIndex +=2u; // will delete last line.
328 if( textInfoIndicesBegin.mCharacterIndex > 0u )
330 // First word needs to be split.
332 // Store text-actors before removing them.
333 CollectTextActors( removedTextActorsFromFirstWord, firstWordLayout, textInfoIndicesBegin.mCharacterIndex, firstWordLayout.mCharactersLayoutInfo.size() );
335 RemoveCharactersFromWord( textInfoIndicesBegin.mCharacterIndex,
336 firstWordLayout.mCharactersLayoutInfo.size() - textInfoIndicesBegin.mCharacterIndex,
339 ++textInfoIndicesBegin.mWordIndex; // will delete from the word after.
342 if( textInfoIndicesEnd.mCharacterIndex + 1u < lastWordLayout.mCharactersLayoutInfo.size() )
344 // Last word needs to be split.
346 // Store text-actors before removing them.
347 CollectTextActors( removedTextActorsFromLastWord, lastWordLayout, 0u, textInfoIndicesEnd.mCharacterIndex + 1u );
349 RemoveCharactersFromWord( 0u,
350 textInfoIndicesEnd.mCharacterIndex + 1u,
355 // This word is going to be merged, so is not needed.
356 ++textInfoIndicesEnd.mWordIndex; // will delete the last word.
359 else if( textInfoIndicesEnd.mCharacterIndex + 1u == lastWordLayout.mCharactersLayoutInfo.size() )
361 // The whole last word is going to be removed.
362 ++textInfoIndicesEnd.mWordIndex; // will delete the last word.
364 if( ( WordSeparator == lastWordLayout.mType ) && mergeWords )
366 // The last word is a word separator and the word after is going to be merged so is not needed.
367 ++textInfoIndicesEnd.mWordIndex; // will delete the word after the last one.
373 // Chraracters to be removed are from the same word.
375 RemoveCharactersFromWordInfo( relayoutData,
379 textInfoIndicesBegin,
381 textInfoMergeIndicesBegin,
382 textInfoMergeIndicesEnd,
384 removedTextActorsFromFirstWord );
388 void SplitLine( const TextInfoIndices& indices,
389 const PointSize& lineHeightOffset,
390 LineLayoutInfo& firstLineLayoutInfo,
391 LineLayoutInfo& lastLineLayoutInfo )
393 // Splits a line in two.
394 // A word may be split in two as well.
396 // * Split the word within the line.
397 // * Add last part of the word to the new line.
398 // * Add words from wordPosition + 1 to the end.
399 // * Update layout info of the last line.
400 // * Remove words added to the last part of the line from the first line.
403 if( ( 0u == indices.mWordIndex ) && ( 0u == indices.mCharacterIndex ) )
405 // the whole line goes to the last part.
406 lastLineLayoutInfo = firstLineLayoutInfo;
408 firstLineLayoutInfo = LineLayoutInfo();
413 if( !firstLineLayoutInfo.mWordsLayoutInfo.empty() )
415 const std::size_t numberOfWords = firstLineLayoutInfo.mWordsLayoutInfo.size();
416 if( indices.mWordIndex == numberOfWords - 1u )
418 const WordLayoutInfo& word( *( firstLineLayoutInfo.mWordsLayoutInfo.end() - 1u ) );
419 if( indices.mCharacterIndex == word.mCharactersLayoutInfo.size() )
421 // the whole line goes to the first part.
423 // Just delete whatever there is in the last part of the line.
424 lastLineLayoutInfo = LineLayoutInfo();
431 lastLineLayoutInfo = LineLayoutInfo();
433 // 1) Split the word whitin the line.
434 WordLayoutInfo& firstWordLayoutInfo( *( firstLineLayoutInfo.mWordsLayoutInfo.begin() + indices.mWordIndex ) );
435 WordLayoutInfo lastWordLayoutInfo;
437 SplitWord( indices.mCharacterIndex,
439 lastWordLayoutInfo );
441 // 2) Add last part of the word to the new line.
442 if( !lastWordLayoutInfo.mCharactersLayoutInfo.empty() )
444 lastLineLayoutInfo.mWordsLayoutInfo.push_back( lastWordLayoutInfo );
447 // 3) Add words from word-position + 1 to the end.
448 lastLineLayoutInfo.mWordsLayoutInfo.insert( lastLineLayoutInfo.mWordsLayoutInfo.end(),
449 firstLineLayoutInfo.mWordsLayoutInfo.begin() + indices.mWordIndex + 1u,
450 firstLineLayoutInfo.mWordsLayoutInfo.end() );
452 // 4) update layout info of the last line.
453 for( WordLayoutInfoContainer::iterator it = lastLineLayoutInfo.mWordsLayoutInfo.begin(), endIt = lastLineLayoutInfo.mWordsLayoutInfo.end();
457 WordLayoutInfo& layoutInfo( *it );
459 UpdateSize( lastLineLayoutInfo.mSize, layoutInfo.mSize );
460 lastLineLayoutInfo.mNumberOfCharacters += layoutInfo.mCharactersLayoutInfo.size();
461 lastLineLayoutInfo.mAscender = std::max( lastLineLayoutInfo.mAscender, layoutInfo.mAscender );
463 lastLineLayoutInfo.mSize.height += lineHeightOffset;
464 lastLineLayoutInfo.mLineHeightOffset = lineHeightOffset;
466 // 5) Remove words added to the last part of the line from the first line.
468 // if the number of characters of the last word of the first line is zero, it should be removed.
469 const std::size_t index = ( firstWordLayoutInfo.mCharactersLayoutInfo.empty() ? indices.mWordIndex : indices.mWordIndex + 1u );
471 firstLineLayoutInfo.mWordsLayoutInfo.erase( firstLineLayoutInfo.mWordsLayoutInfo.begin() + index, firstLineLayoutInfo.mWordsLayoutInfo.end() );
473 // 6) update layout info of the first line.
474 UpdateLayoutInfo( firstLineLayoutInfo, lineHeightOffset );
477 void MergeLine( LineLayoutInfo& firstLineLineLayoutInfo,
478 const LineLayoutInfo& lastLineLayoutInfo )
480 // Merges two given lines.
482 // Can't merge two lines if the last word of the first one is a line separator (new line character)
486 if( lastLineLayoutInfo.mWordsLayoutInfo.empty() )
488 // Nothing to merge if last line is empty.
492 if( firstLineLineLayoutInfo.mWordsLayoutInfo.empty() )
494 // If first line is empty, just copy the last line to the first one.
495 firstLineLineLayoutInfo = lastLineLayoutInfo;
500 // Check the last word of the first line doesn't finish with a new line character.
501 WordLayoutInfo& lastWordLayout( *( firstLineLineLayoutInfo.mWordsLayoutInfo.end() - 1u ) );
502 if( LineSeparator == lastWordLayout.mType )
504 DALI_ASSERT_ALWAYS( !"TextViewProcessor::MergeLine(). ERROR: A line can't be merged to another line which finishes with a new line character." );
507 // If the las word of the first line or the first word of the last line is a white space, both lines can be concatenated.
508 // Otherwise both words need to be merged first.
509 const WordLayoutInfo& firstWordLayout( *lastLineLayoutInfo.mWordsLayoutInfo.begin() );
511 std::size_t index = 0u;
512 if( ( WordSeparator != lastWordLayout.mType ) && ( WordSeparator != firstWordLayout.mType ) && ( LineSeparator != firstWordLayout.mType ) )
514 // Last word of the first line is not a word separator and first word of the last line 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.
526 // Insert the layout of the words.
527 firstLineLineLayoutInfo.mWordsLayoutInfo.insert( firstLineLineLayoutInfo.mWordsLayoutInfo.end(),
528 lastLineLayoutInfo.mWordsLayoutInfo.begin() + index, lastLineLayoutInfo.mWordsLayoutInfo.end() );
530 // Update the size and other layout parameters.
531 UpdateSize( firstLineLineLayoutInfo.mSize, lastLineLayoutInfo.mSize );
532 firstLineLineLayoutInfo.mAscender = std::max( firstLineLineLayoutInfo.mAscender, lastLineLayoutInfo.mAscender );
533 firstLineLineLayoutInfo.mLineHeightOffset = std::max( firstLineLineLayoutInfo.mLineHeightOffset, lastLineLayoutInfo.mLineHeightOffset );
534 firstLineLineLayoutInfo.mNumberOfCharacters += lastLineLayoutInfo.mNumberOfCharacters;
537 WordLayoutInfo GetLastWordLayoutInfo( const LineLayoutInfo& lineLayoutInfo )
539 WordLayoutInfo layoutInfo;
541 if( !lineLayoutInfo.mWordsLayoutInfo.empty() )
543 layoutInfo = *( lineLayoutInfo.mWordsLayoutInfo.end() - 1u );
549 CharacterLayoutInfo GetFirstCharacterLayoutInfo( const LineLayoutInfo& lineLayoutInfo )
551 CharacterLayoutInfo layoutInfo;
553 if( !lineLayoutInfo.mWordsLayoutInfo.empty() )
555 const WordLayoutInfo& wordInfo( *lineLayoutInfo.mWordsLayoutInfo.begin() );
557 layoutInfo = GetFirstCharacterLayoutInfo( wordInfo );
563 CharacterLayoutInfo GetLastCharacterLayoutInfo( const LineLayoutInfo& lineLayoutInfo )
565 const WordLayoutInfo wordInfo = GetLastWordLayoutInfo( lineLayoutInfo );
567 return GetLastCharacterLayoutInfo( wordInfo );
570 void CollectTextActorsFromLines( std::vector<TextActor>& textActors, const TextLayoutInfo& textLayoutInfo, const std::size_t lineIndexBegin, const std::size_t lineIndexEnd )
572 for( LineLayoutInfoContainer::const_iterator lineIt = textLayoutInfo.mLinesLayoutInfo.begin() + lineIndexBegin, lineEndIt = textLayoutInfo.mLinesLayoutInfo.begin() + lineIndexEnd;
576 const LineLayoutInfo& line( *lineIt );
578 CollectTextActorsFromWords( textActors, line, 0u, line.mWordsLayoutInfo.size() );
582 } //namespace TextViewProcessor
584 } //namespace Internal
586 } //namespace Toolkit