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