Add 'ExclusiveArch: armv7l' limit build to arm architecture
[platform/core/uifw/dali-toolkit.git] / dali-toolkit / internal / controls / text-view / text-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 // EXTERNAL INCLUDES
18 #include <fribidi/fribidi.h>
19
20 // INTERNAL INCLUDES
21
22 #include <dali-toolkit/public-api/markup-processor/markup-processor.h>
23
24 namespace Dali
25 {
26
27 namespace Toolkit
28 {
29
30 namespace Internal
31 {
32
33 namespace TextProcessor
34 {
35
36 void SplitInLines( const MarkupProcessor::StyledTextArray& text,
37                    std::vector<MarkupProcessor::StyledTextArray>& lines )
38 {
39   MarkupProcessor::StyledTextArray line;
40   for( MarkupProcessor::StyledTextArray::const_iterator it = text.begin(), endIt = text.end(); it != endIt; ++it )
41   {
42     const MarkupProcessor::StyledText& styledText( *it );
43
44     for( size_t i = 0, length = styledText.mText.GetLength(); i < length; ++i )
45     {
46       const Dali::Character character = styledText.mText[i];
47
48       if( character.IsNewLine() ) // LF
49       {
50         Text newText( character );
51         MarkupProcessor::StyledText newStyledText( newText, styledText.mStyle );
52         line.push_back( newStyledText );
53
54         lines.push_back( line );
55         line.clear();
56       }
57       else
58       {
59         Text newText( character );
60         MarkupProcessor::StyledText newStyledText( newText, styledText.mStyle );
61         line.push_back( newStyledText );
62       }
63     }
64   }
65
66   // This line could be empty if the last character of the previous line is a 'new line' character
67   // and is the last of the text.
68   lines.push_back( line );
69 }
70
71 void SplitInWords( const MarkupProcessor::StyledTextArray& line,
72                    std::vector<MarkupProcessor::StyledTextArray>& words )
73 {
74   MarkupProcessor::StyledTextArray word;
75   for( MarkupProcessor::StyledTextArray::const_iterator it = line.begin(), endIt = line.end(); it != endIt; ++it )
76   {
77     const MarkupProcessor::StyledText& styledText( *it );
78     const Dali::Character character = styledText.mText[0];
79
80     if( character.IsWhiteSpace() )
81     {
82       // When a separator is found, the previous word is added to the list,
83       // then a new word is initialized and the separator is also added as a word.
84       if( !word.empty() )
85       {
86         words.push_back( word );
87         word.clear(); // initializes a new word.
88       }
89
90       // Separator added as a word.
91       MarkupProcessor::StyledText separatorChar;
92       separatorChar.mText.Append( character );
93       separatorChar.mStyle = styledText.mStyle;
94
95       MarkupProcessor::StyledTextArray separatorWord;
96       separatorWord.push_back( separatorChar );
97
98       words.push_back( separatorWord );
99     }
100     else
101     {
102       MarkupProcessor::StyledText styledChar;
103       styledChar.mStyle = styledText.mStyle;
104       styledChar.mText.Append( character );
105
106       // Add the character to the current word.
107       word.push_back( styledChar );
108     }
109   }
110
111   //Finally the last word need to be added.
112   if( !word.empty() )
113   {
114     words.push_back( word );
115   }
116 }
117
118 bool BeginsRightToLeftCharacter( const MarkupProcessor::StyledTextArray& styledText )
119 {
120   for( MarkupProcessor::StyledTextArray::const_iterator it = styledText.begin(), endIt = styledText.end(); it != endIt; ++it )
121   {
122     const Text& text( (*it).mText );
123
124     for( size_t i = 0, length = text.GetLength(); i < length; ++i )
125     {
126       Character::CharacterDirection direction = text[i].GetCharacterDirection();
127       if( direction != Character::Neutral )
128       {
129         return ( direction == Character::RightToLeft || direction == Character::RightToLeftWeak );
130       }
131     }
132   }
133
134   return false;
135 }
136
137 bool BeginsRightToLeftCharacter( const Text& text )
138 {
139   for( size_t i = 0, length = text.GetLength(); i < length; ++i )
140   {
141     Character::CharacterDirection direction = text[i].GetCharacterDirection();
142     if( direction != Character::Neutral )
143     {
144       return ( direction == Character::RightToLeft || direction == Character::RightToLeftWeak );
145     }
146   }
147
148   return false;
149 }
150
151 bool ContainsRightToLeftCharacter( const MarkupProcessor::StyledTextArray& styledText )
152 {
153   for( MarkupProcessor::StyledTextArray::const_iterator it = styledText.begin(), endIt = styledText.end(); it != endIt; ++it )
154   {
155     const Text& text( (*it).mText );
156
157     for( size_t i = 0, length = text.GetLength(); i < length; ++i )
158     {
159       Character::CharacterDirection direction = text[i].GetCharacterDirection();
160       if( ( Character::RightToLeft == direction ) || ( Character::RightToLeftWeak == direction ) )
161       {
162         return true;
163       }
164     }
165   }
166
167   return false;
168 }
169
170 bool ContainsRightToLeftCharacter( const Dali::Text& text )
171 {
172   for( size_t i = 0, length = text.GetLength(); i < length; ++i )
173   {
174     Character::CharacterDirection direction = ( text[i] ).GetCharacterDirection();
175     if( ( Character::RightToLeft == direction ) || ( Character::RightToLeftWeak == direction ) )
176     {
177       return true;
178     }
179   }
180
181   return false;
182 }
183
184 void ConvertBidirectionalText( const MarkupProcessor::StyledTextArray& line,
185                                std::vector<MarkupProcessor::StyledTextArray>& convertedText,
186                                std::vector<int>& logicalToVisualMap,
187                                std::vector<int>& visualToLogicalMap )
188 {
189   // Clean vectors first. This function doesn't use any previous value.
190   logicalToVisualMap.clear();
191   visualToLogicalMap.clear();
192   convertedText.clear();
193
194   if( line.empty() )
195   {
196     // nothing to do if the line is empty.
197     return;
198   }
199
200   // Get the plain text from the line to be reordered by the BiDirectional algorithm.
201   std::string textToBeConverted;
202   GetPlainString( line, textToBeConverted );
203
204   const std::size_t stringSize = textToBeConverted.size();
205
206   std::vector<FriBidiChar> logicalStrBuffer;
207   std::vector<FriBidiChar> visualStrBuffer;
208   // unicode length <= UTF-8 length in bytes (reserve one extra for terminator)
209   // pad these buffers with 0's, as it's unclear what fribidi_log2vis does w.r.t.
210   // the length of it's output content (appears the same as input content, and does
211   // not seem to generate bidi marks i.e. FRIBIDI_CHAR_LRM/FRIBIDI_CHAR_RLM)
212   logicalStrBuffer.resize( stringSize+1, 0 );
213   visualStrBuffer.resize( stringSize+1, 0 );
214   FriBidiChar *logicalStr( &logicalStrBuffer[0] );
215   FriBidiChar *visualStr( &visualStrBuffer[0] );
216
217   // Convert UTF-8 string to unicode string
218   const std::size_t length = fribidi_charset_to_unicode( FRIBIDI_CHAR_SET_UTF8, textToBeConverted.c_str(), stringSize, logicalStr );
219
220   if( 0 == length )
221   {
222     DALI_ASSERT_DEBUG( !"TextProcessor::ConvertBidirectionalText. Error when calling at fribidi_charset_to_unicode" );
223
224     return;
225   }
226
227   logicalToVisualMap.resize( length );
228   visualToLogicalMap.resize( length );
229
230   // Convert and reorder the string as specified by the Unicode Bidirectional Algorithm
231   FriBidiCharType baseDirection = FRIBIDI_TYPE_ON;
232   fribidi_boolean log2vis = fribidi_log2vis( logicalStr, length, &baseDirection, visualStr, &logicalToVisualMap[0], &visualToLogicalMap[0], NULL );
233
234   if(log2vis)
235   {
236     // Convert the unicode string back to the UTF-8 string
237     std::vector<char> bidiTextConverted;
238
239     bidiTextConverted.resize( length * 4 + 1 ); // Maximum bytes to represent one UTF-8 character is 6.
240                                                 // Currently Dali doesn't support this UTF-8 extension. Dali only supports 'regular' UTF-8 which has a maximum of 4 bytes per character.
241
242     fribidi_unicode_to_charset( FRIBIDI_CHAR_SET_UTF8, visualStr, length, &bidiTextConverted[0] );
243
244     textToBeConverted = &bidiTextConverted[0];
245
246     // After reorder the text, rebuild the text with the original styles is needed.
247     // To assign the original style is needed to use the characterLogicalToVisualMap table.
248     Text text( &bidiTextConverted[0] );
249
250     // Split the line in groups of words.
251     // Words are grouped if they can be displayed left to right or right to left.
252     // Add the correct styles for the characters after they are reordered.
253
254     MarkupProcessor::StyledTextArray groupOfWords;
255
256     Character::CharacterDirection previousDirection = ( BeginsRightToLeftCharacter( line ) ? Character::RightToLeft : Character::LeftToRight );
257     for( size_t i = 0; i < length; ++i )
258     {
259       const Character character( text[i] );
260
261       Character::CharacterDirection currentDirection = character.GetCharacterDirection();
262       if( Character::Neutral == currentDirection )
263       {
264         currentDirection = previousDirection;
265       }
266
267       MarkupProcessor::StyledText styledText;
268       styledText.mText.Append( character );
269       styledText.mStyle = line[visualToLogicalMap[i]].mStyle;
270
271       if( currentDirection != previousDirection )
272       {
273         if( !groupOfWords.empty() )
274         {
275           convertedText.push_back( groupOfWords );
276           groupOfWords.clear();
277         }
278       }
279
280       groupOfWords.push_back( styledText );
281
282       previousDirection = currentDirection;
283     }
284
285     if( !groupOfWords.empty() )
286     {
287       convertedText.push_back( groupOfWords );
288     }
289   }
290 }
291
292 bool IsWhiteSpace( const MarkupProcessor::StyledTextArray& text, size_t offset )
293 {
294   DALI_ASSERT_DEBUG( offset < text.size() );
295
296   // assume 1 Character per StyledText
297   return text[offset].mText[0].IsWhiteSpace();
298 }
299
300 void FindNearestWord( const MarkupProcessor::StyledTextArray& text, size_t offset, size_t& start, size_t& end)
301 {
302   const size_t size(text.size());
303   offset = std::min(offset, size-1);
304
305   size_t i(offset);
306   size_t j(offset);
307
308   // if currently looking at whitespace, then search left and right for non-whitespace.
309   if(IsWhiteSpace(text, offset))
310   {
311     // scan left until non-white space / beginning of string.
312     while(i > 0 && IsWhiteSpace(text, i))
313     {
314       i--;
315     }
316
317     // scan right until non-white space / end of string.
318     while(j < size && IsWhiteSpace(text, j))
319     {
320       j++;
321     }
322   }
323
324   // check if r.h.s. word is closer than l.h.s. word
325   if( (j - offset) < // distance to closest right word <
326       (offset - i) ) // distance to closest left word
327   {
328     // point left and right markers on start of right word
329     i = j;
330   }
331   else
332   {
333     // point left and right markers on end of left word
334     j = i;
335   }
336
337   // expand left and right markers to encompase entire word
338   while(i > 0 && !IsWhiteSpace(text, i-1))
339   {
340     i--;
341   }
342
343   while(j < size && !IsWhiteSpace(text, j))
344   {
345     j++;
346   }
347
348   start = i;
349   end = j;
350 }
351
352 } // namespace TextProcessor
353
354 } // namespace Internal
355
356 } // namespace DaliToolkit
357
358 } // namespace Dali