Resolve incorrect position for Ellipsis when mixed LTR & RTL languages and set layout...
[platform/core/uifw/dali-toolkit.git] / dali-toolkit / internal / text / logical-model-impl.cpp
1 /*
2  * Copyright (c) 2022 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 // CLASS HEADER
19 #include <dali-toolkit/internal/text/logical-model-impl.h>
20
21 // INTERNAL INCLUDES
22 #include <dali-toolkit/internal/text/bounded-paragraph-helper-functions.h>
23 #include <dali-toolkit/internal/text/input-style.h>
24 #include <dali-toolkit/internal/text/text-run-container.h>
25
26 namespace Dali
27 {
28 namespace Toolkit
29 {
30 namespace Text
31 {
32 void FreeFontFamilyNames(Vector<FontDescriptionRun>& fontDescriptionRuns)
33 {
34   for(Vector<FontDescriptionRun>::Iterator it    = fontDescriptionRuns.Begin(),
35                                            endIt = fontDescriptionRuns.End();
36       it != endIt;
37       ++it)
38   {
39     delete[](*it).familyName;
40   }
41
42   fontDescriptionRuns.Clear();
43 }
44
45 void FreeEmbeddedItems(Vector<EmbeddedItem>& embeddedItem)
46 {
47   for(Vector<EmbeddedItem>::Iterator it    = embeddedItem.Begin(),
48                                      endIt = embeddedItem.End();
49       it != endIt;
50       ++it)
51   {
52     EmbeddedItem& item = *it;
53     delete[] item.url;
54   }
55
56   embeddedItem.Clear();
57 }
58
59 void FreeAnchors(Vector<Anchor>& anchors)
60 {
61   for(auto&& anchor : anchors)
62   {
63     delete[] anchor.href;
64   }
65
66   anchors.Clear();
67 }
68
69 LogicalModelPtr LogicalModel::New()
70 {
71   return LogicalModelPtr(new LogicalModel());
72 }
73
74 Script LogicalModel::GetScript(CharacterIndex characterIndex) const
75 {
76   // If this operation is too slow, consider a binary search.
77
78   const ScriptRun* const scriptRunBuffer = mScriptRuns.Begin();
79   for(Length index = 0u, length = mScriptRuns.Count(); index < length; ++index)
80   {
81     const ScriptRun* const scriptRun = scriptRunBuffer + index;
82
83     if((scriptRun->characterRun.characterIndex <= characterIndex) &&
84        (characterIndex < scriptRun->characterRun.characterIndex + scriptRun->characterRun.numberOfCharacters))
85     {
86       return scriptRun->script;
87     }
88   }
89
90   return TextAbstraction::UNKNOWN;
91 }
92
93 CharacterDirection LogicalModel::GetCharacterDirection(CharacterIndex characterIndex) const
94 {
95   if(characterIndex >= mCharacterDirections.Count())
96   {
97     // The model has no right to left characters, so the vector of directions is void.
98     return false;
99   }
100
101   return *(mCharacterDirections.Begin() + characterIndex);
102 }
103
104 CharacterIndex LogicalModel::GetLogicalCursorIndex(CharacterIndex visualCursorIndex)
105 {
106   // The character's directions buffer.
107   const CharacterDirection* const modelCharacterDirections = mCharacterDirections.Begin();
108
109   // The bidirectional line info.
110   const BidirectionalLineInfoRun* const bidirectionalLineInfo = mBidirectionalLineInfo.Begin() + mBidirectionalLineIndex;
111
112   // Whether the paragraph starts with a right to left character.
113   const bool isRightToLeftParagraph = bidirectionalLineInfo->direction;
114
115   // The total number of characters of the line.
116   const Length lastCharacterIndex = bidirectionalLineInfo->characterRun.characterIndex + bidirectionalLineInfo->characterRun.numberOfCharacters;
117
118   CharacterIndex logicalCursorIndex = 0u;
119
120   if(bidirectionalLineInfo->characterRun.characterIndex == visualCursorIndex)
121   {
122     if(isRightToLeftParagraph)
123     {
124       logicalCursorIndex = lastCharacterIndex;
125     }
126     else // else logical position is the first of the line.
127     {
128       logicalCursorIndex = bidirectionalLineInfo->characterRun.characterIndex;
129     }
130   }
131   else if(lastCharacterIndex == visualCursorIndex)
132   {
133     if(isRightToLeftParagraph)
134     {
135       logicalCursorIndex = bidirectionalLineInfo->characterRun.characterIndex;
136     }
137     else // else logical position is the number of characters.
138     {
139       logicalCursorIndex = lastCharacterIndex;
140     }
141   }
142   else
143   {
144     // Get the character indexed by  index - 1 and index
145     // and calculate the logical position according the directions of
146     // both characters and the direction of the paragraph.
147
148     const CharacterIndex previousVisualCursorIndex  = visualCursorIndex - 1u;
149     const CharacterIndex previousLogicalCursorIndex = *(bidirectionalLineInfo->visualToLogicalMap + previousVisualCursorIndex - bidirectionalLineInfo->characterRun.characterIndex) + bidirectionalLineInfo->characterRun.characterIndex;
150     const CharacterIndex currentLogicalCursorIndex  = *(bidirectionalLineInfo->visualToLogicalMap + visualCursorIndex - bidirectionalLineInfo->characterRun.characterIndex) + bidirectionalLineInfo->characterRun.characterIndex;
151
152     const CharacterDirection previousCharacterDirection = *(modelCharacterDirections + previousLogicalCursorIndex);
153     const CharacterDirection currentCharacterDirection  = *(modelCharacterDirections + currentLogicalCursorIndex);
154
155     if(previousCharacterDirection == currentCharacterDirection)
156     {
157       // Both glyphs have the same direction.
158       if(previousCharacterDirection)
159       {
160         logicalCursorIndex = previousLogicalCursorIndex;
161       }
162       else
163       {
164         logicalCursorIndex = currentLogicalCursorIndex;
165       }
166     }
167     else
168     {
169       if(isRightToLeftParagraph)
170       {
171         if(currentCharacterDirection)
172         {
173           logicalCursorIndex = currentLogicalCursorIndex + 1u;
174         }
175         else
176         {
177           logicalCursorIndex = previousLogicalCursorIndex;
178         }
179       }
180       else
181       {
182         if(previousCharacterDirection)
183         {
184           logicalCursorIndex = currentLogicalCursorIndex;
185         }
186         else
187         {
188           logicalCursorIndex = previousLogicalCursorIndex + 1u;
189         }
190       }
191     }
192   }
193
194   return logicalCursorIndex;
195 }
196
197 CharacterIndex LogicalModel::GetLogicalCharacterIndex(CharacterIndex visualCharacterIndex)
198 {
199   // The bidirectional line info.
200   const BidirectionalLineInfoRun* const bidirectionalLineInfo = mBidirectionalLineInfo.Begin() + mBidirectionalLineIndex;
201
202   return *(bidirectionalLineInfo->visualToLogicalMap + visualCharacterIndex - bidirectionalLineInfo->characterRun.characterIndex) + bidirectionalLineInfo->characterRun.characterIndex;
203 }
204
205 bool LogicalModel::FetchBidirectionalLineInfo(CharacterIndex characterIndex)
206 {
207   // The number of bidirectional lines.
208   const Length numberOfBidirectionalLines = mBidirectionalLineInfo.Count();
209
210   if(0u == numberOfBidirectionalLines)
211   {
212     // If there is no bidirectional info.
213     return false;
214   }
215
216   // Find the bidi line where the character is laid-out.
217
218   const BidirectionalLineInfoRun* const bidirectionalLineInfoBuffer = mBidirectionalLineInfo.Begin();
219
220   // Check first if the character is in the previously fetched line.
221
222   BidirectionalLineRunIndex bidiLineIndex                 = 0u;
223   CharacterIndex            lastCharacterOfRightToLeftRun = 0u;
224   if(mBidirectionalLineIndex < numberOfBidirectionalLines)
225   {
226     const BidirectionalLineInfoRun& bidiLineRun = *(bidirectionalLineInfoBuffer + mBidirectionalLineIndex);
227
228     const CharacterIndex lastCharacterOfRunPlusOne = bidiLineRun.characterRun.characterIndex + bidiLineRun.characterRun.numberOfCharacters;
229     if((bidiLineRun.characterRun.characterIndex <= characterIndex) &&
230        (characterIndex < lastCharacterOfRunPlusOne))
231     {
232       // The character is in the previously fetched bidi line.
233       return true;
234     }
235     else
236     {
237       // The character is not in the previously fetched line.
238       // Set the bidi line index from where to start the fetch.
239
240       if(characterIndex < bidiLineRun.characterRun.characterIndex)
241       {
242         // Start the fetch from the beginning.
243         bidiLineIndex = 0u;
244       }
245       else
246       {
247         // Start the fetch from the next line.
248         bidiLineIndex                 = mBidirectionalLineIndex + 1u;
249         lastCharacterOfRightToLeftRun = lastCharacterOfRunPlusOne - 1u;
250       }
251     }
252   }
253
254   // The character has not been found in the previously fetched bidi line.
255   for(Vector<BidirectionalLineInfoRun>::ConstIterator it    = bidirectionalLineInfoBuffer + bidiLineIndex,
256                                                       endIt = mBidirectionalLineInfo.End();
257       it != endIt;
258       ++it, ++bidiLineIndex)
259   {
260     const BidirectionalLineInfoRun& bidiLineRun = *it;
261
262     if((lastCharacterOfRightToLeftRun < characterIndex) &&
263        (characterIndex < bidiLineRun.characterRun.characterIndex))
264     {
265       // The character is not inside a bidi line.
266       return false;
267     }
268
269     const CharacterIndex lastCharacterOfRunPlusOne = bidiLineRun.characterRun.characterIndex + bidiLineRun.characterRun.numberOfCharacters;
270     lastCharacterOfRightToLeftRun                  = lastCharacterOfRunPlusOne - 1u;
271     if((bidiLineRun.characterRun.characterIndex <= characterIndex) &&
272        (characterIndex < lastCharacterOfRunPlusOne))
273     {
274       // Bidi line found. Fetch the line.
275       mBidirectionalLineIndex = bidiLineIndex;
276       return true;
277     }
278   }
279
280   return false;
281 }
282
283 BidirectionalLineRunIndex LogicalModel::GetBidirectionalLineInfo() const
284 {
285   return mBidirectionalLineIndex;
286 }
287
288 void LogicalModel::UpdateTextStyleRuns(CharacterIndex index, int numberOfCharacters)
289 {
290   const Length totalNumberOfCharacters = mText.Count();
291
292   // Process the color runs.
293   Vector<ColorRun> removedColorRuns;
294   UpdateCharacterRuns<ColorRun>(index,
295                                 numberOfCharacters,
296                                 totalNumberOfCharacters,
297                                 mColorRuns,
298                                 removedColorRuns);
299
300   // This is needed until now for underline tag in mark-up processor
301   // Process the underlined runs.
302   Vector<UnderlinedCharacterRun> removedUnderlinedCharacterRuns;
303   UpdateCharacterRuns<UnderlinedCharacterRun>(index,
304                                               numberOfCharacters,
305                                               totalNumberOfCharacters,
306                                               mUnderlinedCharacterRuns,
307                                               removedUnderlinedCharacterRuns);
308
309   // Process the strikethrough runs.
310   Vector<StrikethroughCharacterRun> removedStrikethroughCharacterRuns;
311   UpdateCharacterRuns<StrikethroughCharacterRun>(index,
312                                                  numberOfCharacters,
313                                                  totalNumberOfCharacters,
314                                                  mStrikethroughCharacterRuns,
315                                                  removedStrikethroughCharacterRuns);
316
317   // Process the background color runs.
318   Vector<ColorRun> removedBackgroundColorRuns;
319   UpdateCharacterRuns<ColorRun>(index,
320                                 numberOfCharacters,
321                                 totalNumberOfCharacters,
322                                 mBackgroundColorRuns,
323                                 removedBackgroundColorRuns);
324
325   // Process the font description runs.
326   Vector<FontDescriptionRun> removedFontDescriptionRuns;
327   UpdateCharacterRuns<FontDescriptionRun>(index,
328                                           numberOfCharacters,
329                                           totalNumberOfCharacters,
330                                           mFontDescriptionRuns,
331                                           removedFontDescriptionRuns);
332
333   // Free memory allocated for the font family name.
334   FreeFontFamilyNames(removedFontDescriptionRuns);
335
336   // Process the bounded paragraph runs
337   MergeBoundedParagraphRunsWhenRemoveCharacters(mText,
338                                                 index,
339                                                 numberOfCharacters,
340                                                 mBoundedParagraphRuns);
341
342   Vector<BoundedParagraphRun> removedBoundedParagraphRuns;
343   UpdateCharacterRuns<BoundedParagraphRun>(index,
344                                            numberOfCharacters,
345                                            totalNumberOfCharacters,
346                                            mBoundedParagraphRuns,
347                                            removedBoundedParagraphRuns);
348
349   Vector<CharacterSpacingCharacterRun> removedCharacterSpacingCharacterRuns;
350   UpdateCharacterRuns<CharacterSpacingCharacterRun>(index,
351                                                     numberOfCharacters,
352                                                     totalNumberOfCharacters,
353                                                     mCharacterSpacingCharacterRuns,
354                                                     removedCharacterSpacingCharacterRuns);
355 }
356
357 void LogicalModel::RetrieveStyle(CharacterIndex index, InputStyle& style)
358 {
359   unsigned int runIndex = 0u;
360
361   // Set the text color.
362   bool                  colorOverriden  = false;
363   unsigned int          colorIndex      = 0u;
364   const ColorRun* const colorRunsBuffer = mColorRuns.Begin();
365   for(Vector<ColorRun>::ConstIterator it    = colorRunsBuffer,
366                                       endIt = mColorRuns.End();
367       it != endIt;
368       ++it, ++runIndex)
369   {
370     const ColorRun& colorRun = *it;
371
372     if((colorRun.characterRun.characterIndex <= index) &&
373        (index < colorRun.characterRun.characterIndex + colorRun.characterRun.numberOfCharacters))
374     {
375       colorIndex     = runIndex;
376       colorOverriden = true;
377     }
378   }
379
380   // Set the text's color if it's overriden.
381   if(colorOverriden)
382   {
383     style.textColor      = (*(colorRunsBuffer + colorIndex)).color;
384     style.isDefaultColor = false;
385   }
386
387   // Reset the run index.
388   runIndex = 0u;
389
390   // Set the font's parameters.
391   bool                            nameOverriden             = false;
392   bool                            weightOverriden           = false;
393   bool                            widthOverriden            = false;
394   bool                            slantOverriden            = false;
395   bool                            sizeOverriden             = false;
396   unsigned int                    nameIndex                 = 0u;
397   unsigned int                    weightIndex               = 0u;
398   unsigned int                    widthIndex                = 0u;
399   unsigned int                    slantIndex                = 0u;
400   unsigned int                    sizeIndex                 = 0u;
401   const FontDescriptionRun* const fontDescriptionRunsBuffer = mFontDescriptionRuns.Begin();
402   for(Vector<FontDescriptionRun>::ConstIterator it    = fontDescriptionRunsBuffer,
403                                                 endIt = mFontDescriptionRuns.End();
404       it != endIt;
405       ++it, ++runIndex)
406   {
407     const FontDescriptionRun& fontDescriptionRun = *it;
408
409     if((fontDescriptionRun.characterRun.characterIndex <= index) &&
410        (index < fontDescriptionRun.characterRun.characterIndex + fontDescriptionRun.characterRun.numberOfCharacters))
411     {
412       if(fontDescriptionRun.familyDefined)
413       {
414         nameIndex     = runIndex;
415         nameOverriden = true;
416       }
417
418       if(fontDescriptionRun.weightDefined)
419       {
420         weightIndex     = runIndex;
421         weightOverriden = true;
422       }
423
424       if(fontDescriptionRun.widthDefined)
425       {
426         widthIndex     = runIndex;
427         widthOverriden = true;
428       }
429
430       if(fontDescriptionRun.slantDefined)
431       {
432         slantIndex     = runIndex;
433         slantOverriden = true;
434       }
435
436       if(fontDescriptionRun.sizeDefined)
437       {
438         sizeIndex     = runIndex;
439         sizeOverriden = true;
440       }
441     }
442   }
443
444   // Set the font's family name if it's overriden.
445   if(nameOverriden)
446   {
447     const FontDescriptionRun& fontDescriptionRun = *(fontDescriptionRunsBuffer + nameIndex);
448
449     style.familyName      = std::string(fontDescriptionRun.familyName, fontDescriptionRun.familyLength);
450     style.isFamilyDefined = true;
451   }
452
453   // Set the font's weight if it's overriden.
454   if(weightOverriden)
455   {
456     const FontDescriptionRun& fontDescriptionRun = *(fontDescriptionRunsBuffer + weightIndex);
457
458     style.weight          = fontDescriptionRun.weight;
459     style.isWeightDefined = true;
460   }
461
462   // Set the font's width if it's overriden.
463   if(widthOverriden)
464   {
465     const FontDescriptionRun& fontDescriptionRun = *(fontDescriptionRunsBuffer + widthIndex);
466
467     style.width          = fontDescriptionRun.width;
468     style.isWidthDefined = true;
469   }
470
471   // Set the font's slant if it's overriden.
472   if(slantOverriden)
473   {
474     const FontDescriptionRun& fontDescriptionRun = *(fontDescriptionRunsBuffer + slantIndex);
475
476     style.slant          = fontDescriptionRun.slant;
477     style.isSlantDefined = true;
478   }
479
480   // Set the font's size if it's overriden.
481   if(sizeOverriden)
482   {
483     const FontDescriptionRun& fontDescriptionRun = *(fontDescriptionRunsBuffer + sizeIndex);
484
485     style.size          = static_cast<float>(fontDescriptionRun.size) / 64.f;
486     style.isSizeDefined = true;
487   }
488 }
489
490 void LogicalModel::ClearFontDescriptionRuns()
491 {
492   FreeFontFamilyNames(mFontDescriptionRuns);
493 }
494
495 void LogicalModel::ClearStrikethroughRuns()
496 {
497   mStrikethroughCharacterRuns.Clear();
498 }
499
500 void LogicalModel::CreateParagraphInfo(CharacterIndex startIndex,
501                                        Length         numberOfCharacters)
502 {
503   const Length totalNumberOfCharacters = mLineBreakInfo.Count();
504
505   // Count the number of LINE_MUST_BREAK to reserve some space for the vector of paragraph's info.
506   Vector<CharacterIndex> paragraphs;
507   paragraphs.Reserve(numberOfCharacters);
508   const TextAbstraction::LineBreakInfo* lineBreakInfoBuffer       = mLineBreakInfo.Begin();
509   const CharacterIndex                  lastCharacterIndexPlusOne = startIndex + numberOfCharacters;
510   for(Length index = startIndex; index < lastCharacterIndexPlusOne; ++index)
511   {
512     if(TextAbstraction::LINE_MUST_BREAK == *(lineBreakInfoBuffer + index))
513     {
514       paragraphs.PushBack(index);
515     }
516   }
517
518   // Whether the current paragraphs are updated or set from scratch.
519   const bool updateCurrentParagraphs = numberOfCharacters < totalNumberOfCharacters;
520
521   // Reserve space for current paragraphs plus new ones.
522   const Length numberOfNewParagraphs   = paragraphs.Count();
523   const Length totalNumberOfParagraphs = mParagraphInfo.Count() + numberOfNewParagraphs;
524   mParagraphInfo.Resize(totalNumberOfParagraphs);
525
526   ParagraphRun*        paragraphInfoBuffer = NULL;
527   Vector<ParagraphRun> newParagraphs;
528
529   if(updateCurrentParagraphs)
530   {
531     newParagraphs.Resize(numberOfNewParagraphs);
532     paragraphInfoBuffer = newParagraphs.Begin();
533   }
534   else
535   {
536     paragraphInfoBuffer = mParagraphInfo.Begin();
537   }
538
539   // Find where to insert the new paragraphs.
540   ParagraphRunIndex paragraphIndex = 0u;
541   CharacterIndex    firstIndex     = startIndex;
542
543   if(updateCurrentParagraphs)
544   {
545     for(Vector<ParagraphRun>::ConstIterator it    = mParagraphInfo.Begin(),
546                                             endIt = mParagraphInfo.Begin() + totalNumberOfParagraphs - numberOfNewParagraphs;
547         it != endIt;
548         ++it)
549     {
550       const ParagraphRun& paragraph(*it);
551
552       if(startIndex < paragraph.characterRun.characterIndex + paragraph.characterRun.numberOfCharacters)
553       {
554         firstIndex = paragraph.characterRun.characterIndex;
555         break;
556       }
557
558       ++paragraphIndex;
559     }
560   }
561
562   // Create the paragraph info.
563   ParagraphRunIndex newParagraphIndex = 0u;
564   for(Vector<CharacterIndex>::ConstIterator it    = paragraphs.Begin(),
565                                             endIt = paragraphs.End();
566       it != endIt;
567       ++it, ++newParagraphIndex)
568   {
569     const CharacterIndex index = *it;
570
571     ParagraphRun& paragraph                   = *(paragraphInfoBuffer + newParagraphIndex);
572     paragraph.characterRun.characterIndex     = firstIndex;
573     paragraph.characterRun.numberOfCharacters = 1u + index - firstIndex;
574
575     firstIndex += paragraph.characterRun.numberOfCharacters;
576   }
577
578   // Insert the new paragraphs.
579   if(updateCurrentParagraphs)
580   {
581     mParagraphInfo.Insert(mParagraphInfo.Begin() + paragraphIndex,
582                           newParagraphs.Begin(),
583                           newParagraphs.End());
584
585     mParagraphInfo.Resize(totalNumberOfParagraphs);
586
587     // Update the next paragraph indices.
588     for(Vector<ParagraphRun>::Iterator it    = mParagraphInfo.Begin() + paragraphIndex + newParagraphs.Count(),
589                                        endIt = mParagraphInfo.End();
590         it != endIt;
591         ++it)
592     {
593       ParagraphRun& paragraph(*it);
594
595       paragraph.characterRun.characterIndex += numberOfCharacters;
596     }
597   }
598 }
599
600 void LogicalModel::FindParagraphs(CharacterIndex             index,
601                                   Length                     numberOfCharacters,
602                                   Vector<ParagraphRunIndex>& paragraphs)
603 {
604   // Reserve som space for the paragraph indices.
605   paragraphs.Reserve(mParagraphInfo.Count());
606
607   // Traverse the paragraphs to find which ones contain the given characters.
608   ParagraphRunIndex paragraphIndex = 0u;
609   for(Vector<ParagraphRun>::ConstIterator it    = mParagraphInfo.Begin(),
610                                           endIt = mParagraphInfo.End();
611       it != endIt;
612       ++it, ++paragraphIndex)
613   {
614     const ParagraphRun& paragraph(*it);
615
616     if((paragraph.characterRun.characterIndex + paragraph.characterRun.numberOfCharacters > index) &&
617        (paragraph.characterRun.characterIndex < index + numberOfCharacters))
618     {
619       paragraphs.PushBack(paragraphIndex);
620     }
621   }
622 }
623
624 Length LogicalModel::GetNumberOfBoundedParagraphRuns() const
625 {
626   return mBoundedParagraphRuns.Count();
627 }
628
629 const Vector<BoundedParagraphRun>& LogicalModel::GetBoundedParagraphRuns() const
630 {
631   return mBoundedParagraphRuns;
632 }
633
634 Length LogicalModel::GetNumberOfCharacterSpacingCharacterRuns() const
635 {
636   return mCharacterSpacingCharacterRuns.Count();
637 }
638
639 const Vector<CharacterSpacingCharacterRun>& LogicalModel::GetCharacterSpacingCharacterRuns() const
640 {
641   return mCharacterSpacingCharacterRuns;
642 }
643
644 void LogicalModel::ClearEmbeddedImages()
645 {
646   FreeEmbeddedItems(mEmbeddedItems);
647 }
648
649 void LogicalModel::ClearAnchors()
650 {
651   FreeAnchors(mAnchors);
652 }
653
654 LogicalModel::~LogicalModel()
655 {
656   ClearFontDescriptionRuns();
657   ClearEmbeddedImages();
658 }
659
660 LogicalModel::LogicalModel()
661 : mBidirectionalLineIndex(0u)
662 {
663 }
664
665 } // namespace Text
666
667 } // namespace Toolkit
668
669 } // namespace Dali