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