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 <fribidi/fribidi.h>
23 #include <dali-toolkit/public-api/markup-processor/markup-processor.h>
34 namespace TextProcessor
37 void SplitInLines( const MarkupProcessor::StyledTextArray& text,
38 std::vector<MarkupProcessor::StyledTextArray>& lines )
40 MarkupProcessor::StyledTextArray line;
41 for( MarkupProcessor::StyledTextArray::const_iterator it = text.begin(), endIt = text.end(); it != endIt; ++it )
43 const MarkupProcessor::StyledText& styledText( *it );
45 for( size_t i = 0, length = styledText.mText.GetLength(); i < length; ++i )
47 const Dali::Character character = styledText.mText[i];
49 if( character.IsNewLine() ) // LF
51 Text newText( character );
52 MarkupProcessor::StyledText newStyledText( newText, styledText.mStyle );
53 line.push_back( newStyledText );
55 lines.push_back( line );
60 Text newText( character );
61 MarkupProcessor::StyledText newStyledText( newText, styledText.mStyle );
62 line.push_back( newStyledText );
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 );
72 void SplitInWords( const MarkupProcessor::StyledTextArray& line,
73 std::vector<MarkupProcessor::StyledTextArray>& words )
75 MarkupProcessor::StyledTextArray word;
76 for( MarkupProcessor::StyledTextArray::const_iterator it = line.begin(), endIt = line.end(); it != endIt; ++it )
78 const MarkupProcessor::StyledText& styledText( *it );
79 const Dali::Character character = styledText.mText[0];
81 if( character.IsWhiteSpace() )
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.
87 words.push_back( word );
88 word.clear(); // initializes a new word.
91 // Separator added as a word.
92 MarkupProcessor::StyledText separatorChar;
93 separatorChar.mText.Append( character );
94 separatorChar.mStyle = styledText.mStyle;
96 MarkupProcessor::StyledTextArray separatorWord;
97 separatorWord.push_back( separatorChar );
99 words.push_back( separatorWord );
103 MarkupProcessor::StyledText styledChar;
104 styledChar.mStyle = styledText.mStyle;
105 styledChar.mText.Append( character );
107 // Add the character to the current word.
108 word.push_back( styledChar );
112 //Finally the last word need to be added.
115 words.push_back( word );
119 bool BeginsRightToLeftCharacter( const MarkupProcessor::StyledTextArray& styledText )
121 for( MarkupProcessor::StyledTextArray::const_iterator it = styledText.begin(), endIt = styledText.end(); it != endIt; ++it )
123 const Text& text( (*it).mText );
125 for( size_t i = 0, length = text.GetLength(); i < length; ++i )
127 Character::CharacterDirection direction = text[i].GetCharacterDirection();
128 if( direction != Character::Neutral )
130 return ( direction == Character::RightToLeft || direction == Character::RightToLeftWeak );
138 bool BeginsRightToLeftCharacter( const Text& text )
140 for( size_t i = 0, length = text.GetLength(); i < length; ++i )
142 Character::CharacterDirection direction = text[i].GetCharacterDirection();
143 if( direction != Character::Neutral )
145 return ( direction == Character::RightToLeft || direction == Character::RightToLeftWeak );
152 bool ContainsRightToLeftCharacter( const MarkupProcessor::StyledTextArray& styledText )
154 for( MarkupProcessor::StyledTextArray::const_iterator it = styledText.begin(), endIt = styledText.end(); it != endIt; ++it )
156 const Text& text( (*it).mText );
158 for( size_t i = 0, length = text.GetLength(); i < length; ++i )
160 Character::CharacterDirection direction = text[i].GetCharacterDirection();
161 if( ( Character::RightToLeft == direction ) || ( Character::RightToLeftWeak == direction ) )
171 bool ContainsRightToLeftCharacter( const Dali::Text& text )
173 for( size_t i = 0, length = text.GetLength(); i < length; ++i )
175 Character::CharacterDirection direction = ( text[i] ).GetCharacterDirection();
176 if( ( Character::RightToLeft == direction ) || ( Character::RightToLeftWeak == direction ) )
185 void ConvertBidirectionalText( const MarkupProcessor::StyledTextArray& line,
186 std::vector<MarkupProcessor::StyledTextArray>& convertedText,
187 std::vector<int>& logicalToVisualMap,
188 std::vector<int>& visualToLogicalMap )
190 // Clean vectors first. This function doesn't use any previous value.
191 logicalToVisualMap.clear();
192 visualToLogicalMap.clear();
193 convertedText.clear();
197 // nothing to do if the line is empty.
201 // Get the plain text from the line to be reordered by the BiDirectional algorithm.
202 std::string textToBeConverted;
203 GetPlainString( line, textToBeConverted );
205 const std::size_t stringSize = textToBeConverted.size();
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] );
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 );
223 DALI_ASSERT_DEBUG( !"TextProcessor::ConvertBidirectionalText. Error when calling at fribidi_charset_to_unicode" );
228 logicalToVisualMap.resize( length );
229 visualToLogicalMap.resize( length );
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 );
237 // Convert the unicode string back to the UTF-8 string
238 std::vector<char> bidiTextConverted;
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.
243 fribidi_unicode_to_charset( FRIBIDI_CHAR_SET_UTF8, visualStr, length, &bidiTextConverted[0] );
245 textToBeConverted = &bidiTextConverted[0];
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] );
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.
255 MarkupProcessor::StyledTextArray groupOfWords;
257 Character::CharacterDirection previousDirection = ( BeginsRightToLeftCharacter( line ) ? Character::RightToLeft : Character::LeftToRight );
258 for( size_t i = 0; i < length; ++i )
260 const Character character( text[i] );
262 Character::CharacterDirection currentDirection = character.GetCharacterDirection();
263 if( Character::Neutral == currentDirection )
265 currentDirection = previousDirection;
268 MarkupProcessor::StyledText styledText;
269 styledText.mText.Append( character );
270 styledText.mStyle = line[visualToLogicalMap[i]].mStyle;
272 if( currentDirection != previousDirection )
274 if( !groupOfWords.empty() )
276 convertedText.push_back( groupOfWords );
277 groupOfWords.clear();
281 groupOfWords.push_back( styledText );
283 previousDirection = currentDirection;
286 if( !groupOfWords.empty() )
288 convertedText.push_back( groupOfWords );
293 bool IsWhiteSpace( const MarkupProcessor::StyledTextArray& text, size_t offset )
295 DALI_ASSERT_DEBUG( offset < text.size() );
297 // assume 1 Character per StyledText
298 return text[offset].mText[0].IsWhiteSpace();
301 void FindNearestWord( const MarkupProcessor::StyledTextArray& text, size_t offset, size_t& start, size_t& end)
303 const size_t size(text.size());
304 offset = std::min(offset, size-1);
309 // if currently looking at whitespace, then search left and right for non-whitespace.
310 if(IsWhiteSpace(text, offset))
312 // scan left until non-white space / beginning of string.
313 while(i > 0 && IsWhiteSpace(text, i))
318 // scan right until non-white space / end of string.
319 while(j < size && IsWhiteSpace(text, j))
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
329 // point left and right markers on start of right word
334 // point left and right markers on end of left word
338 // expand left and right markers to encompase entire word
339 while(i > 0 && !IsWhiteSpace(text, i-1))
344 while(j < size && !IsWhiteSpace(text, j))
353 } // namespace TextProcessor
355 } // namespace Internal
357 } // namespace DaliToolkit