f3744794478c50d398ce8bf2da151ee592613ab4
[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 // FILE HEADER
19 #include <dali-toolkit/internal/controls/text-view/text-processor.h>
20
21 // EXTERNAL INCLUDES
22 #include <fribidi/fribidi.h>
23
24 namespace Dali
25 {
26
27 namespace Toolkit
28 {
29
30 namespace Internal
31 {
32
33 namespace TextProcessor
34 {
35
36 void SplitInParagraphs( const MarkupProcessor::StyledTextArray& text,
37                    std::vector<MarkupProcessor::StyledTextArray>& paragraphs )
38 {
39   MarkupProcessor::StyledTextArray paragraph;
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 = 0u, 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         paragraph.push_back( newStyledText );
53
54         paragraphs.push_back( paragraph );
55         paragraph.clear();
56       }
57       else
58       {
59         Text newText( character );
60         MarkupProcessor::StyledText newStyledText( newText, styledText.mStyle );
61         paragraph.push_back( newStyledText );
62       }
63     }
64   }
65
66   // This paragraph could be empty if the last character of the previous paragraph is a 'new paragraph' character
67   // and is the last of the text.
68   paragraphs.push_back( paragraph );
69 }
70
71 void SplitInWords( const MarkupProcessor::StyledTextArray& paragraph,
72                    std::vector<MarkupProcessor::StyledTextArray>& words )
73 {
74   MarkupProcessor::StyledTextArray word;
75   for( MarkupProcessor::StyledTextArray::const_iterator it = paragraph.begin(), endIt = paragraph.end(); it != endIt; ++it )
76   {
77     const MarkupProcessor::StyledText& styledText( *it );
78     const Dali::Character character = styledText.mText[0u];
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 = 0u, 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 = 0u, 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 = 0u, 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 = 0u, 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                                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+1u, 0u );
213   visualStrBuffer.resize( stringSize+1u, 0u );
214   FriBidiChar *logicalStr( &logicalStrBuffer[0u] );
215   FriBidiChar *visualStr( &visualStrBuffer[0u] );
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( 0u == 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[0u], &visualToLogicalMap[0u], 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 * 4u + 1u ); // 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[0u] );
243
244     textToBeConverted = &bidiTextConverted[0u];
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[0u] );
249
250     // Split the line in words.
251     // Add the correct styles for the characters after they are reordered.
252
253     for( size_t i = 0u; i < length; ++i )
254     {
255       const Character character( text[i] );
256
257       MarkupProcessor::StyledText styledText;
258       styledText.mText.Append( character );
259       styledText.mStyle = line[visualToLogicalMap[i]].mStyle;
260
261       convertedText.push_back( styledText );
262     }
263   }
264 }
265
266 /**
267  * Wheather the character of the text pointed by the given offset is a white space.
268  *
269  * @param[in] text The text.
270  * @param[in] offset Offset pointing the character.
271  *
272  * @return \e true if the character pointed by the offset is a white space.
273  */
274 bool IsWhiteSpace( const MarkupProcessor::StyledTextArray& text, size_t offset )
275 {
276   DALI_ASSERT_DEBUG( offset < text.size() );
277
278   // assume 1 Character per StyledText
279   return text[offset].mText[0u].IsWhiteSpace();
280 }
281
282 void FindNearestWord( const MarkupProcessor::StyledTextArray& text, size_t offset, size_t& start, size_t& end)
283 {
284   const size_t size(text.size());
285   offset = std::min(offset, size-1u);
286
287   size_t i(offset);
288   size_t j(offset);
289
290   // if currently looking at whitespace, then search left and right for non-whitespace.
291   if(IsWhiteSpace(text, offset))
292   {
293     // scan left until non-white space / beginning of string.
294     while(i > 0u && IsWhiteSpace(text, i))
295     {
296       i--;
297     }
298
299     // scan right until non-white space / end of string.
300     while(j < size && IsWhiteSpace(text, j))
301     {
302       j++;
303     }
304   }
305
306   // check if r.h.s. word is closer than l.h.s. word
307   if( (j - offset) < // distance to closest right word <
308       (offset - i) ) // distance to closest left word
309   {
310     // point left and right markers on start of right word
311     i = j;
312   }
313   else
314   {
315     // point left and right markers on end of left word
316     j = i;
317   }
318
319   // expand left and right markers to encompase entire word
320   while(i > 0u && !IsWhiteSpace(text, i-1u))
321   {
322     i--;
323   }
324
325   while(j < size && !IsWhiteSpace(text, j))
326   {
327     j++;
328   }
329
330   start = i;
331   end = j;
332 }
333
334 } // namespace TextProcessor
335
336 } // namespace Internal
337
338 } // namespace DaliToolkit
339
340 } // namespace Dali