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-paragraph-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 ParagraphLayoutInfo::ParagraphLayoutInfo()
45 mLineHeightOffset( 0.f ),
46 mNumberOfCharacters( 0u ),
51 ParagraphLayoutInfo::~ParagraphLayoutInfo()
55 ParagraphLayoutInfo::ParagraphLayoutInfo( const ParagraphLayoutInfo& paragraph )
56 : mSize( paragraph.mSize ),
57 mAscender( paragraph.mAscender ),
58 mLineHeightOffset( paragraph.mLineHeightOffset ),
59 mNumberOfCharacters( paragraph.mNumberOfCharacters ),
60 mWordsLayoutInfo( paragraph.mWordsLayoutInfo )
64 ParagraphLayoutInfo& ParagraphLayoutInfo::operator=( const ParagraphLayoutInfo& paragraph )
66 mSize = paragraph.mSize;
67 mAscender = paragraph.mAscender;
68 mLineHeightOffset = paragraph.mLineHeightOffset;
69 mNumberOfCharacters = paragraph.mNumberOfCharacters;
70 mWordsLayoutInfo = paragraph.mWordsLayoutInfo;
75 void CreateParagraphInfo( const MarkupProcessor::StyledTextArray& paragraph,
76 TextView::RelayoutData& relayoutData,
77 ParagraphLayoutInfo& paragraphLayoutInfo )
79 // Split the paragraph in words.
80 // TODO: Proper RTL support.
81 MarkupProcessor::StyledTextArray convertedParagraph;
82 if( TextProcessor::ContainsRightToLeftCharacter( paragraph ) )
84 // If the text is bidirectional, the characters will be converted and reordered
85 // as specified by the Unicode Bidirectional Algorithm.
87 // Reorders the paragraph and converts arabic glyphs (if any).
88 TextProcessor::ConvertBidirectionalText( paragraph,
90 relayoutData.mCharacterLogicalToVisualMap,
91 relayoutData.mCharacterVisualToLogicalMap);
95 // No bidirectional text to process.
96 convertedParagraph = paragraph;
98 // Create trivial bidirectional map tables.
99 std::size_t index = 0u;
100 for( MarkupProcessor::StyledTextArray::const_iterator it = convertedParagraph.begin(), endIt = convertedParagraph.end(); it != endIt; ++it )
102 const MarkupProcessor::StyledText& styledText( *it );
104 for( std::size_t i = 0u, length = styledText.mText.GetLength(); i < length; ++i )
106 relayoutData.mCharacterLogicalToVisualMap.push_back( relayoutData.mTextLayoutInfo.mNumberOfCharacters + index );
107 relayoutData.mCharacterVisualToLogicalMap.push_back( relayoutData.mTextLayoutInfo.mNumberOfCharacters + index );
113 // Split the paragraph in words
114 std::vector<MarkupProcessor::StyledTextArray> words;
115 TextProcessor::SplitInWords( convertedParagraph, words );
117 // if last word has a new paragraph separator, create a new word.
120 MarkupProcessor::StyledTextArray& word( *( words.end() - 1u ) );
121 if( word.size() > 1u )
123 // do nothing if the word has only one character.
124 MarkupProcessor::StyledText& styledText( *( word.end() - 1u ) );
125 if( !styledText.mText.IsEmpty() )
127 const std::size_t length = styledText.mText.GetLength();
128 if( styledText.mText[length-1u].IsNewLine() )
130 // Last character of this word is a new paragraph character.
132 // Remove paragraph separator character from current word.
133 styledText.mText.Remove( length - 1u, 1u );
135 // Create a new word with the paragraph separator character.
136 MarkupProcessor::StyledText newParagraphText( Text( styledText.mText[length-1u] ), styledText.mStyle );
138 MarkupProcessor::StyledTextArray newParagraphWord;
139 newParagraphWord.push_back( newParagraphText );
141 words.push_back( newParagraphWord );
147 std::string lastCharacterFont; // Keeps the font used by the last character. It's used to set the font to a word separator.
149 // Traverse all words.
150 for( std::vector<MarkupProcessor::StyledTextArray>::const_iterator wordIt = words.begin(), wordEndIt = words.end(); wordIt != wordEndIt; ++wordIt )
152 const MarkupProcessor::StyledTextArray& word( *wordIt );
154 // Data structures for the new word.
155 WordLayoutInfo wordLayoutInfo;
157 CreateWordTextInfo( word,
160 // 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
161 // avoid 'jumps' of characters when there is a switch between one text-actor per character and one text-actor per line and/or style.
162 if( WordSeparator == wordLayoutInfo.mType )
164 // If current word is a word separator (white space) then the font of the last character is set.
165 for( CharacterLayoutInfoContainer::iterator characterIt = wordLayoutInfo.mCharactersLayoutInfo.begin(), characterEndIt = wordLayoutInfo.mCharactersLayoutInfo.end();
166 characterIt != characterEndIt;
169 CharacterLayoutInfo& characterLayout( *characterIt );
171 characterLayout.mStyledText.mStyle.SetFontName( lastCharacterFont );
176 // kepps the font of the last character.
177 if( !wordLayoutInfo.mCharactersLayoutInfo.empty() )
179 lastCharacterFont = ( *( wordLayoutInfo.mCharactersLayoutInfo.end() - 1u ) ).mStyledText.mStyle.GetFontName();
183 // Update the max word width figure.
184 relayoutData.mTextLayoutInfo.mMaxWordWidth = std::max( relayoutData.mTextLayoutInfo.mMaxWordWidth, wordLayoutInfo.mSize.width );
186 // Update layout info for the current paragraph.
187 paragraphLayoutInfo.mAscender = std::max( paragraphLayoutInfo.mAscender, wordLayoutInfo.mAscender );
188 paragraphLayoutInfo.mNumberOfCharacters += wordLayoutInfo.mCharactersLayoutInfo.size();
189 UpdateSize( paragraphLayoutInfo.mSize, wordLayoutInfo.mSize );
191 // Add the word to the current paragraph.
192 paragraphLayoutInfo.mWordsLayoutInfo.push_back( wordLayoutInfo );
196 void UpdateLayoutInfo( ParagraphLayoutInfo& paragraphLayoutInfo, float lineHeightOffset )
198 // Update layout info.
199 paragraphLayoutInfo.mSize = Size::ZERO;
200 paragraphLayoutInfo.mAscender = 0.f;
201 paragraphLayoutInfo.mNumberOfCharacters = 0u;
202 for( WordLayoutInfoContainer::iterator it = paragraphLayoutInfo.mWordsLayoutInfo.begin(), endIt = paragraphLayoutInfo.mWordsLayoutInfo.end();
206 WordLayoutInfo& word( *it );
208 UpdateSize( paragraphLayoutInfo.mSize, word.mSize );
209 paragraphLayoutInfo.mAscender = std::max( paragraphLayoutInfo.mAscender, word.mAscender );
210 paragraphLayoutInfo.mNumberOfCharacters += word.mCharactersLayoutInfo.size();
213 paragraphLayoutInfo.mSize.height += lineHeightOffset;
214 paragraphLayoutInfo.mLineHeightOffset = lineHeightOffset;
217 void RemoveWordsFromParagraph( std::size_t wordIndex,
218 std::size_t numberOfWords,
219 float lineHeightOffset,
220 ParagraphLayoutInfo& paragraphLayout )
222 // Removes words from a paragraph.
224 // * Check if words or paragraphs can be merged after removing a number of words or a paragraph separator needs to be done outside this method.
226 // Remove words from layout info.
227 paragraphLayout.mWordsLayoutInfo.erase( paragraphLayout.mWordsLayoutInfo.begin() + wordIndex,
228 paragraphLayout.mWordsLayoutInfo.begin() + ( wordIndex + numberOfWords ) );
230 UpdateLayoutInfo( paragraphLayout, lineHeightOffset );
233 void RemoveCharactersFromParagraphInfo( TextView::RelayoutData& relayoutData,
234 const std::size_t numberOfCharacters,
236 bool& mergeParagraphs,
237 TextInfoIndices& textInfoIndicesBegin,
238 TextInfoIndices& textInfoIndicesEnd,
239 TextInfoIndices& textInfoMergeIndicesBegin,
240 TextInfoIndices& textInfoMergeIndicesEnd,
241 ParagraphLayoutInfo& paragraphLayout,
242 std::vector<TextActor>& removedTextActorsFromFirstWord,
243 std::vector<TextActor>& removedTextActorsFromLastWord )
245 const TextLayoutInfo& textLayoutInfo = relayoutData.mTextLayoutInfo;
247 if( textInfoIndicesBegin.mWordIndex < textInfoIndicesEnd.mWordIndex )
249 // Deleted text is from different words. The two different words may be merged.
252 WordLayoutInfo& firstWordLayout( *( paragraphLayout.mWordsLayoutInfo.begin() + textInfoIndicesBegin.mWordIndex ) );
255 WordLayoutInfo& lastWordLayout( *( paragraphLayout.mWordsLayoutInfo.begin() + textInfoIndicesEnd.mWordIndex ) );
257 // whether first or last word need to be split and merged.
258 bool mergeFromBegin = false;
259 bool mergeToEnd = false;
261 if( textInfoIndicesBegin.mCharacterIndex > 0u )
263 // First word is going to be split. It could be merged with the last word.
264 mergeFromBegin = true;
265 textInfoMergeIndicesBegin.mWordIndex = textInfoIndicesBegin.mWordIndex;
267 else if( ( textInfoIndicesBegin.mCharacterIndex == 0u ) && ( textInfoIndicesBegin.mWordIndex > 0u ) )
269 // First word is going to be removed completely.
270 // Check if previous word could be merged.
273 WordLayoutInfo& previousWordLayout( *( paragraphLayout.mWordsLayoutInfo.begin() + textInfoIndicesBegin.mWordIndex - 1u ) );
274 if( WordSeparator != previousWordLayout.mType )
276 // Previous word is not a word separator, so could be merged.
277 mergeFromBegin = true;
278 textInfoMergeIndicesBegin.mWordIndex = textInfoIndicesBegin.mWordIndex - 1u;
284 // First word (or previous one) could be merged. Check if last one could be merged as well.
286 if( textInfoIndicesEnd.mCharacterIndex + 1u < lastWordLayout.mCharactersLayoutInfo.size() )
288 // Last word is going to be split. It could be merged with the first word.
290 textInfoMergeIndicesEnd.mWordIndex = textInfoIndicesEnd.mWordIndex;
292 else if( ( textInfoIndicesEnd.mCharacterIndex + 1u == lastWordLayout.mCharactersLayoutInfo.size() ) && ( textInfoIndicesEnd.mWordIndex + 1u < paragraphLayout.mWordsLayoutInfo.size() ) )
294 // Last word is going to be removed completely.
295 // Check if the word after could be merged.
298 WordLayoutInfo& afterWordLayout( *( paragraphLayout.mWordsLayoutInfo.begin() + textInfoIndicesEnd.mWordIndex + 1u ) );
299 if( WordSeparator != afterWordLayout.mType )
301 // The word after is not a word separator, so could be merged.
303 textInfoMergeIndicesEnd.mWordIndex = textInfoIndicesEnd.mWordIndex + 1u;
307 // Merge words only if both words could be merged.
308 mergeWords = mergeFromBegin && mergeToEnd;
311 if( ( textInfoIndicesEnd.mCharacterIndex + 1u == lastWordLayout.mCharactersLayoutInfo.size() ) && ( textInfoIndicesEnd.mWordIndex + 1u == paragraphLayout.mWordsLayoutInfo.size() ) )
313 // Last word of the paragraph is going to be removed completely.
314 // Check if it's a paragraph separator.
316 if( ParagraphSeparator == lastWordLayout.mType )
318 // The paragraph separator is going to be removed.
319 if( textInfoIndicesBegin.mParagraphIndex + 1u < textLayoutInfo.mParagraphsLayoutInfo.size() )
321 // Paragraph need to be merged.
322 textInfoMergeIndicesBegin.mParagraphIndex = textInfoIndicesBegin.mParagraphIndex;
323 textInfoMergeIndicesEnd.mParagraphIndex = textInfoIndicesBegin.mParagraphIndex + 1u;
324 mergeParagraphs= true;
326 ++textInfoIndicesBegin.mParagraphIndex; // increase both indices,
327 textInfoIndicesEnd.mParagraphIndex +=2u; // will delete last paragraph.
332 if( textInfoIndicesBegin.mCharacterIndex > 0u )
334 // First word needs to be split.
336 // Store text-actors before removing them.
337 CollectTextActors( removedTextActorsFromFirstWord, firstWordLayout, textInfoIndicesBegin.mCharacterIndex, firstWordLayout.mCharactersLayoutInfo.size() );
339 RemoveCharactersFromWord( textInfoIndicesBegin.mCharacterIndex,
340 firstWordLayout.mCharactersLayoutInfo.size() - textInfoIndicesBegin.mCharacterIndex,
343 ++textInfoIndicesBegin.mWordIndex; // will delete from the word after.
346 if( textInfoIndicesEnd.mCharacterIndex + 1u < lastWordLayout.mCharactersLayoutInfo.size() )
348 // Last word needs to be split.
350 // Store text-actors before removing them.
351 CollectTextActors( removedTextActorsFromLastWord, lastWordLayout, 0u, textInfoIndicesEnd.mCharacterIndex + 1u );
353 RemoveCharactersFromWord( 0u,
354 textInfoIndicesEnd.mCharacterIndex + 1u,
359 // This word is going to be merged, so is not needed.
360 ++textInfoIndicesEnd.mWordIndex; // will delete the last word.
363 else if( textInfoIndicesEnd.mCharacterIndex + 1u == lastWordLayout.mCharactersLayoutInfo.size() )
365 // The whole last word is going to be removed.
366 ++textInfoIndicesEnd.mWordIndex; // will delete the last word.
368 if( ( WordSeparator == lastWordLayout.mType ) && mergeWords )
370 // The last word is a word separator and the word after is going to be merged so is not needed.
371 ++textInfoIndicesEnd.mWordIndex; // will delete the word after the last one.
377 // Chraracters to be removed are from the same word.
379 RemoveCharactersFromWordInfo( relayoutData,
383 textInfoIndicesBegin,
385 textInfoMergeIndicesBegin,
386 textInfoMergeIndicesEnd,
388 removedTextActorsFromFirstWord );
392 void SplitParagraph( const TextInfoIndices& indices,
393 float lineHeightOffset,
394 ParagraphLayoutInfo& firstParagraphLayoutInfo,
395 ParagraphLayoutInfo& lastParagraphLayoutInfo )
397 // Splits a paragraph in two.
398 // A word may be split in two as well.
400 // * Split the word within the paragraph.
401 // * Add last part of the word to the new paragraph.
402 // * Add words from wordPosition + 1 to the end.
403 // * Update layout info of the last paragraph.
404 // * Remove words added to the last part of the paragraph from the first paragraph.
407 if( ( 0u == indices.mWordIndex ) && ( 0u == indices.mCharacterIndex ) )
409 // the whole paragraph goes to the last part.
410 lastParagraphLayoutInfo = firstParagraphLayoutInfo;
412 firstParagraphLayoutInfo = ParagraphLayoutInfo();
417 if( !firstParagraphLayoutInfo.mWordsLayoutInfo.empty() )
419 const std::size_t numberOfWords = firstParagraphLayoutInfo.mWordsLayoutInfo.size();
420 if( indices.mWordIndex == numberOfWords - 1u )
422 const WordLayoutInfo& word( *( firstParagraphLayoutInfo.mWordsLayoutInfo.end() - 1u ) );
423 if( indices.mCharacterIndex == word.mCharactersLayoutInfo.size() )
425 // the whole paragraph goes to the first part.
427 // Just delete whatever there is in the last part of the paragraph.
428 lastParagraphLayoutInfo = ParagraphLayoutInfo();
435 lastParagraphLayoutInfo = ParagraphLayoutInfo();
437 // 1) Split the word whitin the paragraph.
438 WordLayoutInfo& firstWordLayoutInfo( *( firstParagraphLayoutInfo.mWordsLayoutInfo.begin() + indices.mWordIndex ) );
439 WordLayoutInfo lastWordLayoutInfo;
441 SplitWord( indices.mCharacterIndex,
443 lastWordLayoutInfo );
445 // 2) Add last part of the word to the new paragraph.
446 if( !lastWordLayoutInfo.mCharactersLayoutInfo.empty() )
448 lastParagraphLayoutInfo.mWordsLayoutInfo.push_back( lastWordLayoutInfo );
451 // 3) Add words from word-position + 1 to the end.
452 lastParagraphLayoutInfo.mWordsLayoutInfo.insert( lastParagraphLayoutInfo.mWordsLayoutInfo.end(),
453 firstParagraphLayoutInfo.mWordsLayoutInfo.begin() + indices.mWordIndex + 1u,
454 firstParagraphLayoutInfo.mWordsLayoutInfo.end() );
456 // 4) update layout info of the last paragraph.
457 for( WordLayoutInfoContainer::iterator it = lastParagraphLayoutInfo.mWordsLayoutInfo.begin(), endIt = lastParagraphLayoutInfo.mWordsLayoutInfo.end();
461 WordLayoutInfo& layoutInfo( *it );
463 UpdateSize( lastParagraphLayoutInfo.mSize, layoutInfo.mSize );
464 lastParagraphLayoutInfo.mNumberOfCharacters += layoutInfo.mCharactersLayoutInfo.size();
465 lastParagraphLayoutInfo.mAscender = std::max( lastParagraphLayoutInfo.mAscender, layoutInfo.mAscender );
467 lastParagraphLayoutInfo.mSize.height += lineHeightOffset;
468 lastParagraphLayoutInfo.mLineHeightOffset = lineHeightOffset;
470 // 5) Remove words added to the last part of the paragraph from the first paragraph.
472 // if the number of characters of the last word of the first paragraph is zero, it should be removed.
473 const std::size_t index = ( firstWordLayoutInfo.mCharactersLayoutInfo.empty() ? indices.mWordIndex : indices.mWordIndex + 1u );
475 firstParagraphLayoutInfo.mWordsLayoutInfo.erase( firstParagraphLayoutInfo.mWordsLayoutInfo.begin() + index, firstParagraphLayoutInfo.mWordsLayoutInfo.end() );
477 // 6) update layout info of the first paragraph.
478 UpdateLayoutInfo( firstParagraphLayoutInfo, lineHeightOffset );
481 void MergeParagraph( ParagraphLayoutInfo& firstParagraphLayoutInfo,
482 const ParagraphLayoutInfo& lastParagraphLayoutInfo )
484 // Merges two given paragraphs.
486 // Can't merge two paragraphs if the last word of the first one is a paragraph separator (new paragraph character)
490 if( lastParagraphLayoutInfo.mWordsLayoutInfo.empty() )
492 // Nothing to merge if last paragraph is empty.
496 if( firstParagraphLayoutInfo.mWordsLayoutInfo.empty() )
498 // If first paragraph is empty, just copy the last paragraph to the first one.
499 firstParagraphLayoutInfo = lastParagraphLayoutInfo;
504 // Check the last word of the first paragraph doesn't finish with a new paragraph character.
505 WordLayoutInfo& lastWordLayout( *( firstParagraphLayoutInfo.mWordsLayoutInfo.end() - 1u ) );
506 if( ParagraphSeparator == lastWordLayout.mType )
508 DALI_ASSERT_ALWAYS( !"TextViewProcessor::MergeParagraph(). ERROR: A paragraph can't be merged to another paragraph which finishes with a new paragraph character." );
511 // If the las word of the first paragraph or the first word of the last paragraph is a white space, both paragraphs can be concatenated.
512 // Otherwise both words need to be merged first.
513 const WordLayoutInfo& firstWordLayout( *lastParagraphLayoutInfo.mWordsLayoutInfo.begin() );
515 std::size_t index = 0u;
516 if( ( WordSeparator != lastWordLayout.mType ) && ( WordSeparator != firstWordLayout.mType ) && ( ParagraphSeparator != firstWordLayout.mType ) )
518 // Last word of the first paragraph is not a word separator and first word of the last paragraph is not a word or paragraph separator.
519 // Words need to be merged.
521 MergeWord( lastWordLayout,
524 // After merging two words, the rest of the words need to be added.
525 ++index; // By increasing this index the word already merged won't be added again.
530 // Insert the layout of the words.
531 firstParagraphLayoutInfo.mWordsLayoutInfo.insert( firstParagraphLayoutInfo.mWordsLayoutInfo.end(),
532 lastParagraphLayoutInfo.mWordsLayoutInfo.begin() + index, lastParagraphLayoutInfo.mWordsLayoutInfo.end() );
534 // Update the size and other layout parameters.
535 UpdateSize( firstParagraphLayoutInfo.mSize, lastParagraphLayoutInfo.mSize );
536 firstParagraphLayoutInfo.mAscender = std::max( firstParagraphLayoutInfo.mAscender, lastParagraphLayoutInfo.mAscender );
537 firstParagraphLayoutInfo.mLineHeightOffset = std::max( firstParagraphLayoutInfo.mLineHeightOffset, lastParagraphLayoutInfo.mLineHeightOffset );
538 firstParagraphLayoutInfo.mNumberOfCharacters += lastParagraphLayoutInfo.mNumberOfCharacters;
541 WordLayoutInfo GetLastWordLayoutInfo( const ParagraphLayoutInfo& paragraphLayoutInfo )
543 WordLayoutInfo layoutInfo;
545 if( !paragraphLayoutInfo.mWordsLayoutInfo.empty() )
547 layoutInfo = *( paragraphLayoutInfo.mWordsLayoutInfo.end() - 1u );
553 CharacterLayoutInfo GetFirstCharacterLayoutInfo( const ParagraphLayoutInfo& paragraphLayoutInfo )
555 CharacterLayoutInfo layoutInfo;
557 if( !paragraphLayoutInfo.mWordsLayoutInfo.empty() )
559 const WordLayoutInfo& wordInfo( *paragraphLayoutInfo.mWordsLayoutInfo.begin() );
561 layoutInfo = GetFirstCharacterLayoutInfo( wordInfo );
567 CharacterLayoutInfo GetLastCharacterLayoutInfo( const ParagraphLayoutInfo& paragraphLayoutInfo )
569 const WordLayoutInfo wordInfo = GetLastWordLayoutInfo( paragraphLayoutInfo );
571 return GetLastCharacterLayoutInfo( wordInfo );
574 void CollectTextActorsFromParagraphs( std::vector<TextActor>& textActors, const TextLayoutInfo& textLayoutInfo, const std::size_t paragraphIndexBegin, const std::size_t paragraphIndexEnd )
576 for( ParagraphLayoutInfoContainer::const_iterator paragraphIt = textLayoutInfo.mParagraphsLayoutInfo.begin() + paragraphIndexBegin, paragraphEndIt = textLayoutInfo.mParagraphsLayoutInfo.begin() + paragraphIndexEnd;
577 paragraphIt != paragraphEndIt;
580 const ParagraphLayoutInfo& paragraph( *paragraphIt );
582 CollectTextActorsFromWords( textActors, paragraph, 0u, paragraph.mWordsLayoutInfo.size() );
586 } //namespace TextViewProcessor
588 } //namespace Internal
590 } //namespace Toolkit