b1426c9afc88b73d35c3a6b8db6179d9b3d2c46c
[platform/core/uifw/dali-toolkit.git] / base / dali-toolkit / internal / controls / text-view / text-view-word-processor.cpp
1 /*
2  * Copyright (c) 2014 Samsung Electronics Co., Ltd.
3  *
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
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  *
16  */
17
18 // FILE HEADER
19 #include <dali-toolkit/internal/controls/text-view/text-view-word-processor.h>
20
21 // INTERNAL INCLUDES
22 #include <dali-toolkit/internal/controls/text-view/text-view-processor-helper-functions.h>
23 #include <dali-toolkit/internal/controls/text-view/text-view-processor-dbg.h>
24
25 namespace Dali
26 {
27
28 namespace Toolkit
29 {
30
31 namespace Internal
32 {
33
34 namespace TextViewProcessor
35 {
36
37 namespace
38 {
39
40 const std::string EMOJI_FONT_NAME( "SamsungEmoji" ); // Emoticons font family name.
41
42 /**
43  * Updates the word size and ascender.
44  *
45  * It's called after deleting some characters.
46  *
47  * @param[in] wordLayout The word layout info.
48  */
49 void UpdateLayoutInfo( WordLayoutInfo& wordLayout )
50 {
51   // Initialize layout info for the whole word.
52   wordLayout.mSize = Size::ZERO;
53   wordLayout.mAscender = 0.f;
54
55   // Traverse the character layout info to update the word layout.
56   for( CharacterLayoutInfoContainer::iterator layoutIt = wordLayout.mCharactersLayoutInfo.begin(), layoutEndIt = wordLayout.mCharactersLayoutInfo.end();
57        layoutIt != layoutEndIt;
58        ++layoutIt )
59   {
60     // Layout info for the current character.
61     CharacterLayoutInfo& layoutInfo( *layoutIt );
62
63     // Update layout info for the current word.
64     UpdateSize( wordLayout.mSize, layoutInfo.mSize );
65     wordLayout.mAscender = std::max( wordLayout.mAscender, layoutInfo.mAscender );
66   }
67 }
68
69 } // namespace
70
71 /////////////////////
72 // Layout info.
73 /////////////////////
74
75 WordLayoutInfo::WordLayoutInfo()
76 : mSize(),
77   mAscender( 0.f ),
78   mType( NoSeparator ),
79   mCharactersLayoutInfo()
80 {
81 }
82
83 WordLayoutInfo::~WordLayoutInfo()
84 {
85 }
86
87 WordLayoutInfo::WordLayoutInfo( const WordLayoutInfo& word )
88 : mSize( word.mSize ),
89   mAscender( word.mAscender ),
90   mType( word.mType ),
91   mCharactersLayoutInfo( word.mCharactersLayoutInfo )
92 {
93 }
94
95 WordLayoutInfo& WordLayoutInfo::operator=( const WordLayoutInfo& word )
96 {
97   mSize = word.mSize;
98   mAscender = word.mAscender;
99   mType = word.mType;
100   mCharactersLayoutInfo = word.mCharactersLayoutInfo;
101
102   return *this;
103 }
104
105 void CreateWordTextInfo( const MarkupProcessor::StyledTextArray& word,
106                          WordLayoutInfo& wordLayoutInfo )
107 {
108   DALI_LOG_INFO( gTextViewProcessorLogFilter, Debug::General, "-->TextViewProcessor::CreateWordTextInfo\n" );
109   // Split in characters.
110   for( MarkupProcessor::StyledTextArray::const_iterator charIt = word.begin(), charEndIt = word.end(); charIt != charEndIt; ++charIt )
111   {
112     const MarkupProcessor::StyledText& styledText( *charIt );
113
114     const std::size_t length = styledText.mText.GetLength();
115
116     // It could be a group of characters.
117     for( std::size_t index = 0u; index < length; ++index )
118     {
119       MarkupProcessor::StyledText styledCharacter;
120       styledCharacter.mStyle = styledText.mStyle;
121       Character character = styledText.mText[index];
122       styledCharacter.mText.Append( character );
123
124       // Create layout character info.
125       CharacterLayoutInfo characterLayoutInfo;
126
127       characterLayoutInfo.mIsColorGlyph = GlyphImage::IsColorGlyph( character );
128       DALI_LOG_INFO( gTextViewProcessorLogFilter, Debug::General, "  Is color glyph: %s\n", ( characterLayoutInfo.mIsColorGlyph ? "True" : "False" ) );
129
130       if( characterLayoutInfo.mIsColorGlyph )
131       {
132         styledCharacter.mStyle.SetFontName( EMOJI_FONT_NAME );
133       }
134       else
135       {
136         //Choose the right font for the given character and style.
137         ChooseFontFamilyName( styledCharacter );
138       }
139
140       // Gets the metrics of the font.
141       const Font font = Font::New( FontParameters( styledCharacter.mStyle.GetFontName(), styledCharacter.mStyle.GetFontStyle(), styledCharacter.mStyle.GetFontPointSize() ) );
142       const Font::Metrics metrics = font.GetMetrics( character );
143       const float ascender = font.GetAscender();
144
145       // The font line's height is used as character's height.
146       characterLayoutInfo.mSize.height = font.GetLineHeight();
147
148       // The character's advance is used as charcter's width.
149       characterLayoutInfo.mSize.width = metrics.GetAdvance();
150
151       // The ascender and bearing are used to position correctly glyphs of different font sizes.
152       characterLayoutInfo.mAscender = ascender;
153       characterLayoutInfo.mBearing = metrics.GetBearing();
154
155       if( character.IsNewLine() && !characterLayoutInfo.mIsColorGlyph )
156       {
157         // A new paragraph character doesn't have any width.
158         characterLayoutInfo.mSize.width = 0.f;
159       }
160
161       // Set's the underline thickness and position.
162       // Both thickness and position includes the vertical pad adjust used in effects like glow or shadow.
163       if( styledCharacter.mStyle.IsUnderlineEnabled() )
164       {
165         characterLayoutInfo.mUnderlineThickness = font.GetUnderlineThickness();
166         characterLayoutInfo.mUnderlinePosition = font.GetUnderlinePosition();
167       }
168
169       // stores the styled text.
170       characterLayoutInfo.mStyledText.mText = styledCharacter.mText;
171       characterLayoutInfo.mStyledText.mStyle = styledCharacter.mStyle;
172
173       // Add character layout info to the word layout info and update it.
174       wordLayoutInfo.mCharactersLayoutInfo.push_back( characterLayoutInfo );
175       UpdateSize( wordLayoutInfo.mSize, characterLayoutInfo.mSize );
176       wordLayoutInfo.mAscender = std::max( wordLayoutInfo.mAscender, characterLayoutInfo.mAscender );
177       wordLayoutInfo.mType = GetTextSeparatorType( character );
178     } // end of each character in the group of characters.
179   } // end of characters in the word.
180   DALI_LOG_INFO( gTextViewProcessorLogFilter, Debug::General, "<--TextViewProcessor::CreateWordTextInfo\n" );
181 }
182
183 void RemoveCharactersFromWordInfo( TextView::RelayoutData& relayoutData,
184                                    const std::size_t numberOfCharacters,
185                                    bool& mergeWords,
186                                    bool& mergeParagraphs,
187                                    TextInfoIndices& textInfoIndicesBegin,
188                                    TextInfoIndices& textInfoIndicesEnd,
189                                    TextInfoIndices& textInfoMergeIndicesBegin,
190                                    TextInfoIndices& textInfoMergeIndicesEnd,
191                                    ParagraphLayoutInfo& paragraphLayout,
192                                    std::vector<TextActor>& removedTextActors )
193 {
194   const TextLayoutInfo& textLayoutInfo = relayoutData.mTextLayoutInfo;
195
196   // Get the word.
197   WordLayoutInfo& wordLayout( *( paragraphLayout.mWordsLayoutInfo.begin() + textInfoIndicesBegin.mWordIndex ) );
198
199   if( ParagraphSeparator == wordLayout.mType )
200   {
201     // If the word is a paragraph separator and there is more paragraphs, then current paragraph and the paragraph after need to be merged.
202     if( textInfoIndicesBegin.mParagraphIndex + 1u < textLayoutInfo.mParagraphsLayoutInfo.size() )
203     {
204       // current paragraph is not the last one.
205
206       // Update indices to merge paragraphs.
207       textInfoMergeIndicesBegin.mParagraphIndex = textInfoIndicesBegin.mParagraphIndex;
208       textInfoMergeIndicesEnd.mParagraphIndex = textInfoIndicesBegin.mParagraphIndex + 1u;
209
210       mergeParagraphs = true;
211
212       ++textInfoIndicesBegin.mParagraphIndex; // increase both indices,
213       textInfoIndicesEnd.mParagraphIndex += 2u; // will delete last paragraph.
214     }
215
216     ++textInfoIndicesEnd.mWordIndex; //will delete the paragraph separator;
217   }
218   else if( WordSeparator == wordLayout.mType )
219   {
220     // If the word is a word separator. Check if the word before and the word after can be merged.
221
222     if( ( 0u < textInfoIndicesBegin.mWordIndex ) && ( paragraphLayout.mWordsLayoutInfo.size() > textInfoIndicesBegin.mWordIndex + 1u ) )
223     {
224       const WordLayoutInfo& wordLayoutBefore( *( paragraphLayout.mWordsLayoutInfo.begin() + textInfoIndicesBegin.mWordIndex - 1u ) );
225       const WordLayoutInfo& wordLayoutAfter( *( paragraphLayout.mWordsLayoutInfo.begin() + textInfoIndicesBegin.mWordIndex + 1u ) );
226
227       if( ( NoSeparator == wordLayoutBefore.mType ) && ( NoSeparator == wordLayoutAfter.mType ) )
228       {
229         // This word is a word separator (white space) and is not the first word of the paragraph nor the last one.
230         mergeWords = true;
231
232         // Set indices to merge the words.
233         textInfoMergeIndicesBegin.mWordIndex = textInfoIndicesBegin.mWordIndex - 1u; // word before word separator.
234         textInfoMergeIndicesEnd.mWordIndex = textInfoIndicesBegin.mWordIndex + 1u; // word after word separator.
235
236         textInfoIndicesEnd.mWordIndex += 2u; // will delete the word separator and the merged word.
237       }
238       else
239       {
240         ++textInfoIndicesEnd.mWordIndex; // will delete the word separator;
241       }
242     }
243     else
244     {
245       ++textInfoIndicesEnd.mWordIndex; // will delete the word separator;
246     }
247   }
248   else if( numberOfCharacters == wordLayout.mCharactersLayoutInfo.size() )
249   {
250     // The whole word needs to be removed.
251     ++textInfoIndicesEnd.mWordIndex; // will delete the current word.
252   }
253   else
254   {
255     // Store text-actors before removing them.
256     CollectTextActors( removedTextActors, wordLayout, textInfoIndicesBegin.mCharacterIndex, textInfoIndicesBegin.mCharacterIndex + numberOfCharacters );
257
258     // just remove some characters from current word.
259     RemoveCharactersFromWord( textInfoIndicesBegin.mCharacterIndex,
260                               numberOfCharacters,
261                               wordLayout );
262   }
263 }
264
265 void RemoveCharactersFromWord( const std::size_t position,
266                                const std::size_t numberOfCharacters,
267                                WordLayoutInfo& wordLayout )
268 {
269   // Removes a given number of characters from the given word starting from the 'position' index.
270
271   // Early return.
272   if( 0u == numberOfCharacters )
273   {
274     // nothing to do if the number of characters is zero.
275
276     return;
277   }
278
279   // Remove characters from layout and text-actor info.
280   wordLayout.mCharactersLayoutInfo.erase( wordLayout.mCharactersLayoutInfo.begin() + position, wordLayout.mCharactersLayoutInfo.begin() + position + numberOfCharacters );
281
282   // Some characters have been removed from the word. Update the layout info is needed.
283   UpdateLayoutInfo( wordLayout );
284 }
285
286 void SplitWord( const std::size_t position,
287                 WordLayoutInfo& firstWordLayoutInfo,
288                 WordLayoutInfo& lastWordLayoutInfo )
289 {
290   // Splits a word in two.
291   // It moves characters from the first part of the word to the last one.
292
293   // early returns
294   if( 0u == position )
295   {
296     // the whole word goes to the last part of the word.
297     lastWordLayoutInfo = firstWordLayoutInfo;
298
299     firstWordLayoutInfo = WordLayoutInfo();
300
301     return;
302   }
303
304   if( position == firstWordLayoutInfo.mCharactersLayoutInfo.size() )
305   {
306     // the whole word goes to the first part of the word.
307
308     // Just delete whatever there is in the last part of the word.
309     lastWordLayoutInfo = WordLayoutInfo();
310
311     return;
312   }
313
314   // Initialize output data structures.
315
316   // Layout info
317   lastWordLayoutInfo = WordLayoutInfo();
318
319   // Split layout info.
320
321   // Insert characters from the given index 'position' to the end.
322   lastWordLayoutInfo.mCharactersLayoutInfo.insert( lastWordLayoutInfo.mCharactersLayoutInfo.end(),
323                                                    firstWordLayoutInfo.mCharactersLayoutInfo.begin() + position, firstWordLayoutInfo.mCharactersLayoutInfo.end() );
324
325   // Delete characters from the first part of the word.
326   firstWordLayoutInfo.mCharactersLayoutInfo.erase( firstWordLayoutInfo.mCharactersLayoutInfo.begin() + position, firstWordLayoutInfo.mCharactersLayoutInfo.end() );
327
328   // Update the layout info of both new words.
329   UpdateLayoutInfo( firstWordLayoutInfo );
330   UpdateLayoutInfo( lastWordLayoutInfo );
331 }
332
333 void MergeWord( WordLayoutInfo& firstWordLayoutInfo,
334                 const WordLayoutInfo& lastWordLayoutInfo )
335 {
336   // Merges two given words.
337
338   // Early returns.
339   if( lastWordLayoutInfo.mCharactersLayoutInfo.empty() )
340   {
341     // nothing to do
342     return;
343   }
344
345   if( firstWordLayoutInfo.mCharactersLayoutInfo.empty() )
346   {
347     // copy last to first
348
349     firstWordLayoutInfo = lastWordLayoutInfo;
350
351     return;
352   }
353
354   if( ( NoSeparator != firstWordLayoutInfo.mType ) || ( NoSeparator != lastWordLayoutInfo.mType ) )
355   {
356     // Do not merge white spaces or new paragraph characters.
357     DALI_ASSERT_ALWAYS( !"TextViewProcessor::MergeWord(). ERROR: White spaces or new paragraph characters can't be merged with other words." );
358   }
359
360   // Merge layout info
361   firstWordLayoutInfo.mCharactersLayoutInfo.insert( firstWordLayoutInfo.mCharactersLayoutInfo.end(),
362                                                     lastWordLayoutInfo.mCharactersLayoutInfo.begin(),
363                                                     lastWordLayoutInfo.mCharactersLayoutInfo.end() );
364
365   // Update the word layout info.
366   UpdateSize( firstWordLayoutInfo.mSize, lastWordLayoutInfo.mSize );
367   firstWordLayoutInfo.mAscender = std::max( firstWordLayoutInfo.mAscender, lastWordLayoutInfo.mAscender );
368 }
369
370 CharacterLayoutInfo GetFirstCharacterLayoutInfo( const WordLayoutInfo& wordLayoutInfo )
371 {
372   CharacterLayoutInfo layoutInfo;
373
374   if( !wordLayoutInfo.mCharactersLayoutInfo.empty() )
375   {
376     layoutInfo = *wordLayoutInfo.mCharactersLayoutInfo.begin();
377   }
378
379   return layoutInfo;
380 }
381
382 CharacterLayoutInfo GetLastCharacterLayoutInfo( const WordLayoutInfo& wordLayoutInfo )
383 {
384   CharacterLayoutInfo layoutInfo;
385
386   if( !wordLayoutInfo.mCharactersLayoutInfo.empty() )
387   {
388     layoutInfo = *( wordLayoutInfo.mCharactersLayoutInfo.end() - 1u );
389   }
390
391   return layoutInfo;
392 }
393
394 void CollectTextActors( std::vector<TextActor>& textActors, const WordLayoutInfo& word, const std::size_t characterIndexBegin, const std::size_t characterIndexEnd )
395 {
396   for( CharacterLayoutInfoContainer::const_iterator characterIt = word.mCharactersLayoutInfo.begin() + characterIndexBegin,
397          characterEndIt = word.mCharactersLayoutInfo.begin() + characterIndexEnd;
398        characterIt != characterEndIt;
399        ++characterIt )
400   {
401     const CharacterLayoutInfo& characterLayout( *characterIt );
402
403     if( !characterLayout.mIsColorGlyph )
404     {
405       TextActor textActor = TextActor::DownCast( characterLayout.mGlyphActor );
406       if( textActor )
407       {
408         textActors.push_back( textActor );
409       }
410     }
411   }
412 }
413
414 void CollectTextActorsFromWords( std::vector<TextActor>& textActors, const ParagraphLayoutInfo& paragraph, const std::size_t wordIndexBegin, const std::size_t wordIndexEnd )
415 {
416   for( WordLayoutInfoContainer::const_iterator wordIt = paragraph.mWordsLayoutInfo.begin() + wordIndexBegin, wordEndIt = paragraph.mWordsLayoutInfo.begin() + wordIndexEnd;
417        wordIt != wordEndIt;
418        ++wordIt )
419   {
420     const WordLayoutInfo& word( *wordIt );
421
422     for( CharacterLayoutInfoContainer::const_iterator characterIt = word.mCharactersLayoutInfo.begin(), characterEndIt = word.mCharactersLayoutInfo.end();
423          characterIt != characterEndIt;
424          ++characterIt )
425     {
426       const CharacterLayoutInfo& characterLayout( *characterIt );
427
428       if( !characterLayout.mIsColorGlyph )
429       {
430         TextActor textActor = TextActor::DownCast( characterLayout.mGlyphActor );
431         if( textActor )
432         {
433           textActors.push_back( textActor );
434         }
435       }
436     }
437   }
438 }
439
440 } //namespace TextViewProcessor
441
442 } //namespace Internal
443
444 } //namespace Toolkit
445
446 } //namespace Dali