18f670f5c35cf35e1f7c75afe5d9d131769bdccb
[platform/core/uifw/dali-toolkit.git] / base / dali-toolkit / internal / controls / text-view / text-view-line-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-line-processor.h>
20
21 // INTERNAL INCLUDES
22 #include <dali-toolkit/internal/controls/text-view/text-view-word-processor.h>
23 #include <dali-toolkit/internal/controls/text-view/text-view-word-group-processor.h>
24 #include <dali-toolkit/internal/controls/text-view/text-view-processor-helper-functions.h>
25 #include <dali-toolkit/internal/controls/text-view/text-processor.h>
26
27 namespace Dali
28 {
29
30 namespace Toolkit
31 {
32
33 namespace Internal
34 {
35
36 namespace TextViewProcessor
37 {
38
39 /////////////////////
40 // Layout info.
41 /////////////////////
42
43 LineLayoutInfo::LineLayoutInfo()
44 : mSize(),
45   mAscender( 0.f ),
46   mLineHeightOffset( 0.f ),
47   mWordGroupsLayoutInfo(),
48   mNumberOfCharacters( 0 )
49 {
50 }
51
52 LineLayoutInfo::LineLayoutInfo( const LineLayoutInfo& line )
53 : mSize( line.mSize ),
54   mAscender( line.mAscender ),
55   mLineHeightOffset( line.mLineHeightOffset ),
56   mWordGroupsLayoutInfo( line.mWordGroupsLayoutInfo ),
57   mNumberOfCharacters( line.mNumberOfCharacters )
58 {
59 }
60
61 LineLayoutInfo& LineLayoutInfo::operator=( const LineLayoutInfo& line )
62 {
63   mSize = line.mSize;
64   mAscender = line.mAscender;
65   mLineHeightOffset = line.mLineHeightOffset;
66   mWordGroupsLayoutInfo = line.mWordGroupsLayoutInfo;
67   mNumberOfCharacters = line.mNumberOfCharacters;
68
69   return *this;
70 }
71
72 void UpdateLineLayoutInfo( TextViewProcessor::LineLayoutInfo& lineLayoutInfo, const float lineHeightOffset )
73 {
74   lineLayoutInfo.mSize = Size();
75
76   for( WordGroupLayoutInfoContainer::iterator it = lineLayoutInfo.mWordGroupsLayoutInfo.begin(), endIt = lineLayoutInfo.mWordGroupsLayoutInfo.end();
77        it != endIt;
78        ++it )
79   {
80     WordGroupLayoutInfo& layoutInfo( *it );
81
82     UpdateSize( lineLayoutInfo.mSize, layoutInfo.mSize );
83   }
84   lineLayoutInfo.mSize.height += lineHeightOffset;
85 }
86
87 void CreateLineInfo( const MarkupProcessor::StyledTextArray& line,
88                      TextView::RelayoutData& relayoutData,
89                      TextViewProcessor::LineLayoutInfo& lineLayoutInfo )
90 {
91   // TODO: Split the line in group of words. Each group of words has only left to right characters or right to left characters but not a mix of both.
92   // TODO: set the wordgroup direction (LTR or RTL)
93   std::vector<MarkupProcessor::StyledTextArray> wordGroups;
94   if( TextProcessor::ContainsRightToLeftCharacter( line ) )
95   {
96     // If the text is bidirectional, the characters will be converted and reordered
97     // as specified by the Unicode Bidirectional Algorithm.
98
99     // Reorders the line and converts arabic glyphs (if any).
100     // It also split words in different groups if there are a mix of left to right
101     // and right to left text.
102     // If the whole line is left to right or right to left all words are grouped in the same group.
103     TextProcessor::ConvertBidirectionalText( line,
104                                              wordGroups,
105                                              relayoutData.mCharacterLogicalToVisualMap,
106                                              relayoutData.mCharacterVisualToLogicalMap);
107   }
108   else
109   {
110     // No bidirectional text to process.
111
112     if( !line.empty() )
113     {
114       // Add all words in a group.
115       wordGroups.push_back( line );
116
117       // Create trivial bidirectional map tables.
118       std::size_t index = 0;
119       for( MarkupProcessor::StyledTextArray::const_iterator it = line.begin(), endIt = line.end(); it != endIt; ++it )
120       {
121         const MarkupProcessor::StyledText& styledText( *it );
122
123         for( std::size_t i = 0, length = styledText.mText.GetLength(); i < length; ++i )
124         {
125           relayoutData.mCharacterLogicalToVisualMap.push_back( relayoutData.mTextLayoutInfo.mNumberOfCharacters + index );
126           relayoutData.mCharacterVisualToLogicalMap.push_back( relayoutData.mTextLayoutInfo.mNumberOfCharacters + index );
127           ++index;
128         }
129       }
130     }
131   }
132
133   // Traverse all group of words.
134   for( std::vector<MarkupProcessor::StyledTextArray>::const_iterator groupIt = wordGroups.begin(), groupEndIt = wordGroups.end(); groupIt != groupEndIt; ++groupIt )
135   {
136     const MarkupProcessor::StyledTextArray& wordGroup( *groupIt );
137
138     // Data structures for the new group of words.
139     WordGroupLayoutInfo wordGroupLayoutInfo;
140
141     CreateWordGroupInfo( wordGroup,
142                          relayoutData.mTextLayoutInfo,
143                          wordGroupLayoutInfo );
144
145     // Update layout info for the current line.
146     lineLayoutInfo.mAscender = std::max( lineLayoutInfo.mAscender, wordGroupLayoutInfo.mAscender );
147     lineLayoutInfo.mNumberOfCharacters += wordGroupLayoutInfo.mNumberOfCharacters;
148     UpdateSize( lineLayoutInfo.mSize, wordGroupLayoutInfo.mSize );
149
150     // Add the group of words to the current line.
151     lineLayoutInfo.mWordGroupsLayoutInfo.push_back( wordGroupLayoutInfo );
152   } // end of group of words
153 }
154
155 void RemoveWordGroupsFromLine( const std::size_t groupIndex,
156                                const std::size_t numberOfGroups,
157                                const PointSize& lineHeightOffset,
158                                LineLayoutInfo& lineLayout )
159 {
160   // Removes groups of words from a line.
161
162   // * Check if words or lines can be merged after removing a group of words or a line separator have to be done outside this method.
163
164   // * Note: Currently it's only used to remove a number of groups of words from the beginning, or
165   //         from groupIndex index to the end. This function doesn't merge groups of words (if a whole group is removed)
166   //         TODO: merge groups of words if required.
167
168   const std::size_t groupEndIndex = groupIndex + numberOfGroups;
169
170   // Remove word groups from layout info.
171   lineLayout.mWordGroupsLayoutInfo.erase( lineLayout.mWordGroupsLayoutInfo.begin() + groupIndex,
172                                           lineLayout.mWordGroupsLayoutInfo.begin() + groupEndIndex );
173
174   // Update layout info.
175   lineLayout.mSize = Size();
176   lineLayout.mAscender = 0.f;
177   lineLayout.mNumberOfCharacters = 0;
178   for( WordGroupLayoutInfoContainer::const_iterator it = lineLayout.mWordGroupsLayoutInfo.begin(), endIt = lineLayout.mWordGroupsLayoutInfo.end();
179        it != endIt;
180        ++it )
181   {
182     const WordGroupLayoutInfo& group( *it );
183
184     UpdateSize( lineLayout.mSize, group.mSize );
185     lineLayout.mAscender = std::max( lineLayout.mAscender, group.mAscender );
186     lineLayout.mNumberOfCharacters += group.mNumberOfCharacters;
187   }
188   lineLayout.mSize.height += lineHeightOffset;
189   lineLayout.mLineHeightOffset = lineHeightOffset;
190 }
191
192 void SplitLine( const TextInfoIndices& indices,
193                 const PointSize& lineHeightOffset,
194                 LineLayoutInfo& firstLineLayoutInfo,
195                 LineLayoutInfo& lastLineLayoutInfo )
196 {
197   // Splits a line in two.
198   // A group of words and a word may be split in two as well.
199
200   // * Split the group of words within the line.
201   // * Add last part of the group of words to the new line.
202   // * Add groups of words from groupPosition + 1 to the end.
203   // * Update layout info of the last line.
204   // * Remove groups of words added to the last part of the line from the first line.
205
206   // early returns!!
207   if( ( 0 == indices.mGroupIndex ) && ( 0 == indices.mWordIndex ) && ( 0 == indices.mCharacterIndex ) )
208   {
209     // the whole line goes to the last part.
210     lastLineLayoutInfo = firstLineLayoutInfo;
211
212     firstLineLayoutInfo = LineLayoutInfo();
213
214     return;
215   }
216
217   if( !firstLineLayoutInfo.mWordGroupsLayoutInfo.empty() )
218   {
219     const std::size_t numberOfGroups = firstLineLayoutInfo.mWordGroupsLayoutInfo.size();
220     if( indices.mGroupIndex == numberOfGroups - 1 )
221     {
222       const WordGroupLayoutInfo& group( *( firstLineLayoutInfo.mWordGroupsLayoutInfo.end() - 1 ) );
223
224       const std::size_t numberOfWords = group.mWordsLayoutInfo.size();
225       if( indices.mWordIndex == numberOfWords - 1 )
226       {
227         const WordLayoutInfo& word( *( group.mWordsLayoutInfo.end() - 1 ) );
228         if( indices.mCharacterIndex == word.mCharactersLayoutInfo.size() )
229         {
230           // the whole line goes to the first part.
231
232           // Just delete whatever there is in the last part of the line.
233           lastLineLayoutInfo = LineLayoutInfo();
234
235           return;
236         }
237       }
238     }
239   }
240
241   lastLineLayoutInfo = LineLayoutInfo();
242
243   // 1) Split the group of words whitin the line.
244   WordGroupLayoutInfo& firstWordGroupLayoutInfo( *( firstLineLayoutInfo.mWordGroupsLayoutInfo.begin() + indices.mGroupIndex ) );
245   WordGroupLayoutInfo lastWordGroupLayoutInfo;
246
247   SplitWordGroup( indices,
248                   firstWordGroupLayoutInfo,
249                   lastWordGroupLayoutInfo );
250
251   // 2) Add last part of the group of words to the new line.
252   if( !lastWordGroupLayoutInfo.mWordsLayoutInfo.empty() )
253   {
254     lastLineLayoutInfo.mWordGroupsLayoutInfo.push_back( lastWordGroupLayoutInfo );
255   }
256
257   // 3) Add groups from group-position + 1 to the end.
258   lastLineLayoutInfo.mWordGroupsLayoutInfo.insert( lastLineLayoutInfo.mWordGroupsLayoutInfo.end(),
259                                                    firstLineLayoutInfo.mWordGroupsLayoutInfo.begin() + indices.mGroupIndex + 1, firstLineLayoutInfo.mWordGroupsLayoutInfo.end() );
260
261   // 4) update layout info of the last line.
262   for( WordGroupLayoutInfoContainer::iterator it = lastLineLayoutInfo.mWordGroupsLayoutInfo.begin(), endIt = lastLineLayoutInfo.mWordGroupsLayoutInfo.end();
263        it != endIt;
264        ++it )
265   {
266     WordGroupLayoutInfo& layoutInfo( *it );
267
268     lastLineLayoutInfo.mNumberOfCharacters += layoutInfo.mNumberOfCharacters;
269     UpdateSize( lastLineLayoutInfo.mSize, layoutInfo.mSize );
270     lastLineLayoutInfo.mAscender = std::max( lastLineLayoutInfo.mAscender, layoutInfo.mAscender );
271   }
272   lastLineLayoutInfo.mSize.height += lineHeightOffset;
273   lastLineLayoutInfo.mLineHeightOffset = lineHeightOffset;
274
275   // 5) Remove groups of words added to the last part of the line from the first line.
276
277   // if the number of characters of the last group of words of the first line is zero, it should be removed.
278   const std::size_t index = ( 0 == firstWordGroupLayoutInfo.mNumberOfCharacters ? indices.mGroupIndex : indices.mGroupIndex + 1 );
279
280   firstLineLayoutInfo.mWordGroupsLayoutInfo.erase( firstLineLayoutInfo.mWordGroupsLayoutInfo.begin() + index, firstLineLayoutInfo.mWordGroupsLayoutInfo.end() );
281
282   // 6) update layout info of the first line.
283   firstLineLayoutInfo.mNumberOfCharacters = 0;
284   firstLineLayoutInfo.mSize = Size();
285   firstLineLayoutInfo.mAscender = 0.f;
286   for( WordGroupLayoutInfoContainer::iterator it = firstLineLayoutInfo.mWordGroupsLayoutInfo.begin(), endIt = firstLineLayoutInfo.mWordGroupsLayoutInfo.end();
287        it != endIt;
288        ++it )
289   {
290     WordGroupLayoutInfo& layoutInfo( *it );
291
292     firstLineLayoutInfo.mNumberOfCharacters += layoutInfo.mNumberOfCharacters;
293     UpdateSize( firstLineLayoutInfo.mSize, layoutInfo.mSize );
294     firstLineLayoutInfo.mAscender = std::max( firstLineLayoutInfo.mAscender, layoutInfo.mAscender );
295   }
296   firstLineLayoutInfo.mSize.height += lineHeightOffset;
297   firstLineLayoutInfo.mLineHeightOffset = lineHeightOffset;
298 }
299
300 void MergeLine( LineLayoutInfo& firstLineLineLayoutInfo,
301                 const LineLayoutInfo& lastLineLayoutInfo )
302 {
303   // Merges two given lines.
304   //
305   // Can't merge two lines if the last word of the first one is a line separator (new line character)
306
307   // Early returns.
308
309   if( lastLineLayoutInfo.mWordGroupsLayoutInfo.empty() )
310   {
311     // Nothing to merge if last line is empty.
312     return;
313   }
314
315   if( firstLineLineLayoutInfo.mWordGroupsLayoutInfo.empty() )
316   {
317     // If first line is empty, just copy the last line to the first one.
318     firstLineLineLayoutInfo = lastLineLayoutInfo;
319
320     return;
321   }
322
323   if( 1 == firstLineLineLayoutInfo.mWordGroupsLayoutInfo.size() )
324   {
325     WordGroupLayoutInfo& wordGroupLayout( *firstLineLineLayoutInfo.mWordGroupsLayoutInfo.begin() );
326
327     if( wordGroupLayout.mWordsLayoutInfo.empty() )
328     {
329       // If first line is empty, just copy the last line to the first one.
330       firstLineLineLayoutInfo = lastLineLayoutInfo;
331
332       return;
333     }
334   }
335
336   // Check the last word of the last group of the first line doesn't finish with a new line character.
337   WordGroupLayoutInfo& lastWordGroupLayout( *( firstLineLineLayoutInfo.mWordGroupsLayoutInfo.end() - 1 ) );
338   WordLayoutInfo& lastWordLayout( *( lastWordGroupLayout.mWordsLayoutInfo.end() - 1 ) );
339   if( LineSeparator == lastWordLayout.mType )
340   {
341     DALI_ASSERT_ALWAYS( !"TextViewProcessor::MergeLine(). ERROR: A line can't be merged to another line which finishes with a new line character." );
342   }
343
344   // If the last group of the first line has the same direction than the first group of the last line, both lines can be concatenated.
345   // Otherwise, both groups have to be merged first.
346   const WordGroupLayoutInfo& firstWordGroupLayout( *lastLineLayoutInfo.mWordGroupsLayoutInfo.begin() );
347
348   std::size_t index = 0;
349   if( lastWordGroupLayout.mDirection == firstWordGroupLayout.mDirection )
350   {
351     // Both groups of words have the same direction. They need to be merged.
352     MergeWordGroup( lastWordGroupLayout,
353                     firstWordGroupLayout );
354
355     // After merging two groups of words, the rest of groups need to be added.
356     ++index; // By increasing this index the group of words already merged won't be added again.
357   }
358
359   // Merge layout info
360   firstLineLineLayoutInfo.mWordGroupsLayoutInfo.insert( firstLineLineLayoutInfo.mWordGroupsLayoutInfo.end(),
361                                                         lastLineLayoutInfo.mWordGroupsLayoutInfo.begin() + index, lastLineLayoutInfo.mWordGroupsLayoutInfo.end() );
362   UpdateSize( firstLineLineLayoutInfo.mSize, lastLineLayoutInfo.mSize );
363   firstLineLineLayoutInfo.mAscender = std::max( firstLineLineLayoutInfo.mAscender, lastLineLayoutInfo.mAscender );
364   firstLineLineLayoutInfo.mLineHeightOffset = std::max( firstLineLineLayoutInfo.mLineHeightOffset, lastLineLayoutInfo.mLineHeightOffset );
365   firstLineLineLayoutInfo.mNumberOfCharacters += lastLineLayoutInfo.mNumberOfCharacters;
366
367 }
368
369 WordLayoutInfo GetLastWordLayoutInfo( const LineLayoutInfo& lineLayoutInfo )
370 {
371   WordLayoutInfo layoutInfo;
372
373   if( !lineLayoutInfo.mWordGroupsLayoutInfo.empty() )
374   {
375     const WordGroupLayoutInfo& groupInfo( *( lineLayoutInfo.mWordGroupsLayoutInfo.end() - 1 ) );
376
377     if( !groupInfo.mWordsLayoutInfo.empty() )
378     {
379       layoutInfo = *( groupInfo.mWordsLayoutInfo.end() - 1 );
380     }
381   }
382
383   return layoutInfo;
384 }
385
386 CharacterLayoutInfo GetFirstCharacterLayoutInfo( const LineLayoutInfo& lineLayoutInfo )
387 {
388   CharacterLayoutInfo layoutInfo;
389
390   if( !lineLayoutInfo.mWordGroupsLayoutInfo.empty() )
391   {
392     const WordGroupLayoutInfo& groupInfo( *lineLayoutInfo.mWordGroupsLayoutInfo.begin() );
393
394     if( !groupInfo.mWordsLayoutInfo.empty() )
395     {
396       const WordLayoutInfo& wordInfo( *groupInfo.mWordsLayoutInfo.begin() );
397
398       layoutInfo = GetFirstCharacterLayoutInfo( wordInfo );
399     }
400   }
401
402   return layoutInfo;
403 }
404
405 CharacterLayoutInfo GetLastCharacterLayoutInfo( const LineLayoutInfo& lineLayoutInfo )
406 {
407   const WordLayoutInfo wordInfo = GetLastWordLayoutInfo( lineLayoutInfo );
408
409   return GetLastCharacterLayoutInfo( wordInfo );
410 }
411
412 void CollectTextActorsFromLines( std::vector<TextActor>& textActors, const TextLayoutInfo& textLayoutInfo, const std::size_t lineIndexBegin, const std::size_t lineIndexEnd )
413 {
414   for( LineLayoutInfoContainer::const_iterator lineIt = textLayoutInfo.mLinesLayoutInfo.begin() + lineIndexBegin, lineEndIt = textLayoutInfo.mLinesLayoutInfo.begin() + lineIndexEnd;
415        lineIt != lineEndIt;
416        ++lineIt )
417   {
418     const LineLayoutInfo& line( *lineIt );
419
420     CollectTextActorsFromGroups( textActors, line, 0, line.mWordGroupsLayoutInfo.size() );
421   }
422 }
423
424 } //namespace TextViewProcessor
425
426 } //namespace Internal
427
428 } //namespace Toolkit
429
430 } //namespace Dali