2 * Copyright (c) 2014 Samsung Electronics Co., Ltd.
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
19 #include <dali-toolkit/internal/controls/text-view/text-processor.h>
22 #include <fribidi/fribidi.h>
33 namespace TextProcessor
36 void SplitInLines( const MarkupProcessor::StyledTextArray& text,
37 std::vector<MarkupProcessor::StyledTextArray>& lines )
39 MarkupProcessor::StyledTextArray line;
40 for( MarkupProcessor::StyledTextArray::const_iterator it = text.begin(), endIt = text.end(); it != endIt; ++it )
42 const MarkupProcessor::StyledText& styledText( *it );
44 for( size_t i = 0, length = styledText.mText.GetLength(); i < length; ++i )
46 const Dali::Character character = styledText.mText[i];
48 if( character.IsNewLine() ) // LF
50 Text newText( character );
51 MarkupProcessor::StyledText newStyledText( newText, styledText.mStyle );
52 line.push_back( newStyledText );
54 lines.push_back( line );
59 Text newText( character );
60 MarkupProcessor::StyledText newStyledText( newText, styledText.mStyle );
61 line.push_back( newStyledText );
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 );
71 void SplitInWords( const MarkupProcessor::StyledTextArray& line,
72 std::vector<MarkupProcessor::StyledTextArray>& words )
74 MarkupProcessor::StyledTextArray word;
75 for( MarkupProcessor::StyledTextArray::const_iterator it = line.begin(), endIt = line.end(); it != endIt; ++it )
77 const MarkupProcessor::StyledText& styledText( *it );
78 const Dali::Character character = styledText.mText[0];
80 if( character.IsWhiteSpace() )
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.
86 words.push_back( word );
87 word.clear(); // initializes a new word.
90 // Separator added as a word.
91 MarkupProcessor::StyledText separatorChar;
92 separatorChar.mText.Append( character );
93 separatorChar.mStyle = styledText.mStyle;
95 MarkupProcessor::StyledTextArray separatorWord;
96 separatorWord.push_back( separatorChar );
98 words.push_back( separatorWord );
102 MarkupProcessor::StyledText styledChar;
103 styledChar.mStyle = styledText.mStyle;
104 styledChar.mText.Append( character );
106 // Add the character to the current word.
107 word.push_back( styledChar );
111 //Finally the last word need to be added.
114 words.push_back( word );
118 bool BeginsRightToLeftCharacter( const MarkupProcessor::StyledTextArray& styledText )
120 for( MarkupProcessor::StyledTextArray::const_iterator it = styledText.begin(), endIt = styledText.end(); it != endIt; ++it )
122 const Text& text( (*it).mText );
124 for( size_t i = 0, length = text.GetLength(); i < length; ++i )
126 Character::CharacterDirection direction = text[i].GetCharacterDirection();
127 if( direction != Character::Neutral )
129 return ( direction == Character::RightToLeft || direction == Character::RightToLeftWeak );
137 bool BeginsRightToLeftCharacter( const Text& text )
139 for( size_t i = 0, length = text.GetLength(); i < length; ++i )
141 Character::CharacterDirection direction = text[i].GetCharacterDirection();
142 if( direction != Character::Neutral )
144 return ( direction == Character::RightToLeft || direction == Character::RightToLeftWeak );
151 bool ContainsRightToLeftCharacter( const MarkupProcessor::StyledTextArray& styledText )
153 for( MarkupProcessor::StyledTextArray::const_iterator it = styledText.begin(), endIt = styledText.end(); it != endIt; ++it )
155 const Text& text( (*it).mText );
157 for( size_t i = 0, length = text.GetLength(); i < length; ++i )
159 Character::CharacterDirection direction = text[i].GetCharacterDirection();
160 if( ( Character::RightToLeft == direction ) || ( Character::RightToLeftWeak == direction ) )
170 bool ContainsRightToLeftCharacter( const Dali::Text& text )
172 for( size_t i = 0, length = text.GetLength(); i < length; ++i )
174 Character::CharacterDirection direction = ( text[i] ).GetCharacterDirection();
175 if( ( Character::RightToLeft == direction ) || ( Character::RightToLeftWeak == direction ) )
184 void ConvertBidirectionalText( const MarkupProcessor::StyledTextArray& line,
185 std::vector<MarkupProcessor::StyledTextArray>& convertedText,
186 std::vector<int>& logicalToVisualMap,
187 std::vector<int>& visualToLogicalMap )
189 // Clean vectors first. This function doesn't use any previous value.
190 logicalToVisualMap.clear();
191 visualToLogicalMap.clear();
192 convertedText.clear();
196 // nothing to do if the line is empty.
200 // Get the plain text from the line to be reordered by the BiDirectional algorithm.
201 std::string textToBeConverted;
202 GetPlainString( line, textToBeConverted );
204 const std::size_t stringSize = textToBeConverted.size();
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] );
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 );
222 DALI_ASSERT_DEBUG( !"TextProcessor::ConvertBidirectionalText. Error when calling at fribidi_charset_to_unicode" );
227 logicalToVisualMap.resize( length );
228 visualToLogicalMap.resize( length );
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 );
236 // Convert the unicode string back to the UTF-8 string
237 std::vector<char> bidiTextConverted;
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.
242 fribidi_unicode_to_charset( FRIBIDI_CHAR_SET_UTF8, visualStr, length, &bidiTextConverted[0] );
244 textToBeConverted = &bidiTextConverted[0];
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] );
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.
254 MarkupProcessor::StyledTextArray groupOfWords;
256 Character::CharacterDirection previousDirection = ( BeginsRightToLeftCharacter( line ) ? Character::RightToLeft : Character::LeftToRight );
257 for( size_t i = 0; i < length; ++i )
259 const Character character( text[i] );
261 Character::CharacterDirection currentDirection = character.GetCharacterDirection();
262 if( Character::Neutral == currentDirection )
264 currentDirection = previousDirection;
267 MarkupProcessor::StyledText styledText;
268 styledText.mText.Append( character );
269 styledText.mStyle = line[visualToLogicalMap[i]].mStyle;
271 if( currentDirection != previousDirection )
273 if( !groupOfWords.empty() )
275 convertedText.push_back( groupOfWords );
276 groupOfWords.clear();
280 groupOfWords.push_back( styledText );
282 previousDirection = currentDirection;
285 if( !groupOfWords.empty() )
287 convertedText.push_back( groupOfWords );
292 bool IsWhiteSpace( const MarkupProcessor::StyledTextArray& text, size_t offset )
294 DALI_ASSERT_DEBUG( offset < text.size() );
296 // assume 1 Character per StyledText
297 return text[offset].mText[0].IsWhiteSpace();
300 void FindNearestWord( const MarkupProcessor::StyledTextArray& text, size_t offset, size_t& start, size_t& end)
302 const size_t size(text.size());
303 offset = std::min(offset, size-1);
308 // if currently looking at whitespace, then search left and right for non-whitespace.
309 if(IsWhiteSpace(text, offset))
311 // scan left until non-white space / beginning of string.
312 while(i > 0 && IsWhiteSpace(text, i))
317 // scan right until non-white space / end of string.
318 while(j < size && IsWhiteSpace(text, j))
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
328 // point left and right markers on start of right word
333 // point left and right markers on end of left word
337 // expand left and right markers to encompase entire word
338 while(i > 0 && !IsWhiteSpace(text, i-1))
343 while(j < size && !IsWhiteSpace(text, j))
352 } // namespace TextProcessor
354 } // namespace Internal
356 } // namespace DaliToolkit