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