fix issue in negative line spacing with key arrow down
[platform/core/uifw/dali-toolkit.git] / dali-toolkit / devel-api / text / text-utils-devel.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 // FILE HEADER
19 #include <dali-toolkit/devel-api/text/text-utils-devel.h>
20
21 // EXTERNAL INCLUDES
22 #include <dali/devel-api/scripting/enum-helper.h>
23 #include <dali/devel-api/text-abstraction/font-client.h>
24 #include <dali/devel-api/text-abstraction/text-renderer-layout-helper.h>
25 #include <dali/devel-api/text-abstraction/text-renderer.h>
26 #include <dali/integration-api/debug.h>
27 #include <cstring>
28 #include <limits>
29
30 // INTERNAL INCLUDES
31 #include <dali-toolkit/internal/text/bidirectional-support.h>
32 #include <dali-toolkit/internal/text/character-set-conversion.h>
33 #include <dali-toolkit/internal/text/color-segmentation.h>
34 #include <dali-toolkit/internal/text/glyph-metrics-helper.h>
35 #include <dali-toolkit/internal/text/layouts/layout-engine.h>
36 #include <dali-toolkit/internal/text/layouts/layout-parameters.h>
37 #include <dali-toolkit/internal/text/markup-processor.h>
38 #include <dali-toolkit/internal/text/multi-language-support.h>
39 #include <dali-toolkit/internal/text/segmentation.h>
40 #include <dali-toolkit/internal/text/shaper.h>
41 #include <dali-toolkit/internal/text/text-enumerations-impl.h>
42 #include <dali-toolkit/internal/text/text-font-style.h>
43 #include <dali-toolkit/internal/text/text-model.h>
44
45 namespace Dali
46 {
47 using namespace TextAbstraction;
48
49 namespace Toolkit
50 {
51 using namespace Text;
52
53 namespace DevelText
54 {
55 namespace Layout
56 {
57 /**
58  * @brief The text's layout.
59  */
60 enum Type
61 {
62   SINGLELINE, ///< The text is laid out on a single line.
63   MULTILINE,  ///< The text is laid out in multiple lines.
64   CIRCULAR,   ///< The text is laid out on a single line following a circular path.
65 };
66
67 } // namespace Layout
68
69 namespace CircularAlignment
70 {
71 /**
72  * @brief The enumerations for the circular alignment.
73  */
74 enum Type
75 {
76   BEGIN,  ///< The text is aligned to the @p begin angle of the arc (or to the @p begin+increment if it's a RTL text).
77   CENTER, ///< The text is centered within the arc.
78   END,    ///< The text is aligned to the @p begin+increment angle of the arc (or to the @p begin if it's a RTL text).
79 };
80
81 } // namespace CircularAlignment
82
83 const float TO_POINT_26_DOT_6 = 64.f;
84 const float TO_FLOAT          = 1.f / 255.f;
85 const float TO_UCHAR          = 255.f;
86 const bool  RTL               = true;
87 const float TWO_PI            = 2.f * Dali::Math::PI;    ///< 360 degrees in radians
88 const float RAD_135           = Math::PI_2 + Math::PI_4; ///< 135 degrees in radians;
89 const float RAD_225           = RAD_135 + Math::PI_2;    ///< 225 degrees in radians;
90 const float RAD_270           = 3.f * Math::PI_2;        ///< 270 degrees in radians;
91 const float RAD_315           = RAD_225 + Math::PI_2;    ///< 315 degrees in radians;
92 const float MAX_INT           = std::numeric_limits<int>::max();
93
94 DALI_ENUM_TO_STRING_TABLE_BEGIN(LAYOUT_TYPE)
95   DALI_ENUM_TO_STRING_WITH_SCOPE(DevelText::Layout, SINGLELINE)
96   DALI_ENUM_TO_STRING_WITH_SCOPE(DevelText::Layout, MULTILINE)
97   DALI_ENUM_TO_STRING_WITH_SCOPE(DevelText::Layout, CIRCULAR)
98 DALI_ENUM_TO_STRING_TABLE_END(LAYOUT_TYPE)
99
100 DALI_ENUM_TO_STRING_TABLE_BEGIN(CIRCULAR_ALIGNMENT_TYPE)
101   DALI_ENUM_TO_STRING_WITH_SCOPE(DevelText::CircularAlignment, BEGIN)
102   DALI_ENUM_TO_STRING_WITH_SCOPE(DevelText::CircularAlignment, CENTER)
103   DALI_ENUM_TO_STRING_WITH_SCOPE(DevelText::CircularAlignment, END)
104 DALI_ENUM_TO_STRING_TABLE_END(CIRCULAR_ALIGNMENT_TYPE)
105
106 struct InternalDataModel
107 {
108   InternalDataModel(FontClient&    fontClient,
109                     MetricsPtr     metrics,
110                     Text::ModelPtr textModel)
111   : fontClient(fontClient),
112     metrics(metrics),
113     textModel(textModel),
114     numberOfCharacters{0u},
115     isTextMirrored{false},
116     numberOfGlyphs{0u}
117   {
118     layoutEngine.SetMetrics(metrics);
119   }
120
121   FontClient&               fontClient;
122   MetricsPtr                metrics;
123   Text::Layout::Engine      layoutEngine; ///< The layout engine.
124   Text::ModelPtr            textModel;    ///< Pointer to the text's model.
125   Vector<ColorBlendingMode> blendingMode; ///< How embedded items and bitmap font glyphs are blended with color text.
126   Vector<bool>              isEmoji;      ///< Whether the glyph is an emoji.
127
128   Vector<Character> mirroredUtf32Characters; // The utf32Characters Characters but mirrored if there are RTL text.
129
130   Length numberOfCharacters; // The number of characters (not glyphs!).
131   bool   isTextMirrored;     // Whether the text has been mirrored.
132
133   Length numberOfGlyphs;
134   Size   textLayoutArea;
135 };
136
137 bool GetLayoutEnumeration(const Property::Value& propertyValue, DevelText::Layout::Type& layout)
138 {
139   return Scripting::GetEnumerationProperty(propertyValue, LAYOUT_TYPE_TABLE, LAYOUT_TYPE_TABLE_COUNT, layout);
140 }
141
142 bool GetCircularAlignmentEnumeration(const Property::Value& propertyValue, DevelText::CircularAlignment::Type& circularAlignment)
143 {
144   return Scripting::GetEnumerationProperty(propertyValue, CIRCULAR_ALIGNMENT_TYPE_TABLE, CIRCULAR_ALIGNMENT_TYPE_TABLE_COUNT, circularAlignment);
145 }
146
147 void ShapeTextPreprocess(const RendererParameters& textParameters, TextAbstraction::TextRenderer::Parameters& rendererParameters, InternalDataModel& internalDataModel)
148 {
149   MultilanguageSupport multilanguageSupport = MultilanguageSupport::Get();
150   const uint8_t*       utf8                 = NULL; // pointer to the first character of the text (encoded in utf8)
151   Length               textSize             = 0u;   // The length of the utf8 string.
152
153   Length&            numberOfCharacters      = internalDataModel.numberOfCharacters;
154   Vector<Character>& mirroredUtf32Characters = internalDataModel.mirroredUtf32Characters;
155   Text::ModelPtr&    textModel               = internalDataModel.textModel;
156
157   Vector<Character>&                     utf32Characters     = textModel->mLogicalModel->mText;                       // Characters encoded in utf32.
158   Vector<LineBreakInfo>&                 lineBreakInfo       = textModel->mLogicalModel->mLineBreakInfo;              // The line break info.
159   Vector<ScriptRun>&                     scripts             = textModel->mLogicalModel->mScriptRuns;                 // Charactes's script.
160   Vector<FontDescriptionRun>&            fontDescriptionRuns = textModel->mLogicalModel->mFontDescriptionRuns;        // Desired font descriptions.
161   Vector<FontRun>&                       validFonts          = textModel->mLogicalModel->mFontRuns;                   // Validated fonts.
162   Vector<BidirectionalParagraphInfoRun>& bidirectionalInfo   = textModel->mLogicalModel->mBidirectionalParagraphInfo; // The bidirectional info per paragraph.
163   Vector<CharacterDirection>&            directions          = textModel->mLogicalModel->mCharacterDirections;        // Character's directions.
164   Vector<ColorRun>&                      colorRuns           = textModel->mLogicalModel->mColorRuns;                  // colors of the text.
165
166   // the default font's description.
167   FontDescription defaultFontDescription;
168   PointSize26Dot6 defaultPointSize = FontClient::DEFAULT_POINT_SIZE;
169
170   ////////////////////////////////////////////////////////////////////////////////
171   // Process the markup string if the mark-up processor is enabled.
172   ////////////////////////////////////////////////////////////////////////////////
173
174   MarkupProcessData markupProcessData(colorRuns,
175                                       fontDescriptionRuns,
176                                       textModel->mLogicalModel->mEmbeddedItems,
177                                       textModel->mLogicalModel->mAnchors,
178                                       textModel->mLogicalModel->mUnderlinedCharacterRuns,
179                                       textModel->mLogicalModel->mBackgroundColorRuns,
180                                       textModel->mLogicalModel->mStrikethroughCharacterRuns,
181                                       textModel->mLogicalModel->mBoundedParagraphRuns);
182
183   if(textParameters.markupEnabled)
184   {
185     ProcessMarkupString(textParameters.text, markupProcessData);
186     textSize = markupProcessData.markupProcessedText.size();
187
188     // This is a bit horrible but std::string returns a (signed) char*
189     utf8 = reinterpret_cast<const uint8_t*>(markupProcessData.markupProcessedText.c_str());
190   }
191   else
192   {
193     textSize = textParameters.text.size();
194
195     // This is a bit horrible but std::string returns a (signed) char*
196     utf8 = reinterpret_cast<const uint8_t*>(textParameters.text.c_str());
197   }
198
199   ////////////////////////////////////////////////////////////////////////////////
200   // Convert from utf8 to utf32
201   ////////////////////////////////////////////////////////////////////////////////
202
203   utf32Characters.Resize(textSize);
204
205   // Transform a text array encoded in utf8 into an array encoded in utf32.
206   // It returns the actual number of characters.
207   numberOfCharacters = Utf8ToUtf32(utf8, textSize, utf32Characters.Begin());
208   utf32Characters.Resize(numberOfCharacters);
209
210   ////////////////////////////////////////////////////////////////////////////////
211   // Retrieve the Line and Word Break Info.
212   ////////////////////////////////////////////////////////////////////////////////
213
214   lineBreakInfo.Resize(numberOfCharacters, LINE_NO_BREAK);
215
216   SetLineBreakInfo(utf32Characters,
217                    0u,
218                    numberOfCharacters,
219                    lineBreakInfo);
220
221   ////////////////////////////////////////////////////////////////////////////////
222   // Retrieve the script runs.
223   ////////////////////////////////////////////////////////////////////////////////
224
225   multilanguageSupport.SetScripts(utf32Characters,
226                                   0u,
227                                   numberOfCharacters,
228                                   scripts);
229
230   // Check if there are emojis.
231   // If there are an RGBA8888 pixel format is needed.
232   for(const auto& run : scripts)
233   {
234     if(run.script == TextAbstraction::Script::EMOJI)
235     {
236       rendererParameters.pixelFormat = TextAbstraction::TextRenderer::Parameters::RGBA8888;
237       break;
238     }
239   }
240
241   ////////////////////////////////////////////////////////////////////////////////
242   // Retrieve the font runs.
243   ////////////////////////////////////////////////////////////////////////////////
244
245   // Set the description font run with the given text parameters.
246   FontDescriptionRun fontDescriptionRun;
247   fontDescriptionRun.characterRun.characterIndex     = 0u;
248   fontDescriptionRun.characterRun.numberOfCharacters = numberOfCharacters;
249
250   fontDescriptionRun.familyLength  = 0u;
251   fontDescriptionRun.familyName    = nullptr;
252   fontDescriptionRun.familyDefined = !textParameters.fontFamily.empty();
253   if(fontDescriptionRun.familyDefined)
254   {
255     // The allocated memory will be freed when the logical model is destroyed.
256     fontDescriptionRun.familyLength = textParameters.fontFamily.size();
257     fontDescriptionRun.familyName   = new char[fontDescriptionRun.familyLength];
258     memcpy(fontDescriptionRun.familyName, textParameters.fontFamily.c_str(), fontDescriptionRun.familyLength);
259   }
260
261   fontDescriptionRun.weightDefined = !textParameters.fontWeight.empty();
262   if(fontDescriptionRun.weightDefined)
263   {
264     fontDescriptionRun.weight = StringToWeight(textParameters.fontWeight.c_str());
265   }
266
267   fontDescriptionRun.widthDefined = !textParameters.fontWidth.empty();
268   if(fontDescriptionRun.widthDefined)
269   {
270     fontDescriptionRun.width = StringToWidth(textParameters.fontWidth.c_str());
271   }
272
273   fontDescriptionRun.slantDefined = !textParameters.fontSlant.empty();
274   if(fontDescriptionRun.slantDefined)
275   {
276     fontDescriptionRun.slant = StringToSlant(textParameters.fontSlant.c_str());
277   }
278
279   fontDescriptionRun.sizeDefined = !EqualsZero(textParameters.fontSize);
280   if(fontDescriptionRun.sizeDefined)
281   {
282     fontDescriptionRun.size = static_cast<unsigned int>(textParameters.fontSize * TO_POINT_26_DOT_6);
283   }
284
285   fontDescriptionRuns.PushBack(fontDescriptionRun);
286
287   // Validates the fonts. If there is a character with no assigned font it sets a default one.
288   // After this call, fonts are validated.
289   multilanguageSupport.ValidateFonts(utf32Characters,
290                                      scripts,
291                                      fontDescriptionRuns,
292                                      defaultFontDescription,
293                                      defaultPointSize,
294                                      0u,
295                                      numberOfCharacters,
296                                      validFonts);
297
298   ////////////////////////////////////////////////////////////////////////////////
299   // Retrieve the Bidirectional info.
300   ////////////////////////////////////////////////////////////////////////////////
301
302   bidirectionalInfo.Reserve(1u);
303
304   SetBidirectionalInfo(utf32Characters,
305                        scripts,
306                        lineBreakInfo,
307                        0u,
308                        numberOfCharacters,
309                        bidirectionalInfo);
310
311   const bool hasBidirectionalText = 0u != bidirectionalInfo.Count();
312   if(hasBidirectionalText)
313   {
314     // Only set the character directions if there is right to left characters.
315     GetCharactersDirection(bidirectionalInfo,
316                            numberOfCharacters,
317                            0u,
318                            numberOfCharacters,
319                            directions);
320
321     // This paragraph has right to left text. Some characters may need to be mirrored.
322     // TODO: consider if the mirrored string can be stored as well.
323
324     internalDataModel.isTextMirrored = GetMirroredText(utf32Characters,
325                                                        directions,
326                                                        bidirectionalInfo,
327                                                        0u,
328                                                        numberOfCharacters,
329                                                        mirroredUtf32Characters);
330   }
331 }
332
333 void ShapeText(TextAbstraction::TextRenderer::Parameters& rendererParameters, Vector<EmbeddedItemInfo>& embeddedItemLayout, InternalDataModel& internalDataModel)
334 {
335   Vector<GlyphIndex>           newParagraphGlyphs; // Glyphs for the new paragraph characters.
336   const Length                 numberOfCharacters      = internalDataModel.numberOfCharacters;
337   const bool                   isTextMirrored          = internalDataModel.isTextMirrored;
338   Text::ModelPtr&              textModel               = internalDataModel.textModel;
339   const Vector<Character>&     mirroredUtf32Characters = internalDataModel.mirroredUtf32Characters;
340   FontClient&                  fontClient              = internalDataModel.fontClient;
341   const Vector<Character>&     utf32Characters         = textModel->mLogicalModel->mText;          // Characters encoded in utf32.
342   const Vector<LineBreakInfo>& lineBreakInfo           = textModel->mLogicalModel->mLineBreakInfo; // The line break info.
343   const Vector<ScriptRun>&     scripts                 = textModel->mLogicalModel->mScriptRuns;    // Charactes's script.
344   const Vector<FontRun>&       validFonts              = textModel->mLogicalModel->mFontRuns;      // Validated fonts.
345
346   Vector<CharacterIndex>& glyphsToCharacters = textModel->mVisualModel->mGlyphsToCharacters; // Glyphs to character map.
347   Vector<Length>&         charactersPerGlyph = textModel->mVisualModel->mCharactersPerGlyph; // Number of characters per glyph.
348
349   ////////////////////////////////////////////////////////////////////////////////
350   // Retrieve the glyphs. Text shaping
351   ////////////////////////////////////////////////////////////////////////////////
352
353   const Vector<Character>& textToShape = isTextMirrored ? mirroredUtf32Characters : utf32Characters;
354
355   newParagraphGlyphs.Reserve(1u);
356
357   // Shapes the text.
358   ShapeText(textToShape,
359             lineBreakInfo,
360             scripts,
361             validFonts,
362             0u,
363             0u,
364             numberOfCharacters,
365             rendererParameters.glyphs,
366             glyphsToCharacters,
367             charactersPerGlyph,
368             newParagraphGlyphs);
369
370   // Create the 'number of glyphs' per character and the glyph to character conversion tables.
371   textModel->mVisualModel->CreateGlyphsPerCharacterTable(0u, 0u, numberOfCharacters);
372   textModel->mVisualModel->CreateCharacterToGlyphTable(0u, 0u, numberOfCharacters);
373
374   internalDataModel.numberOfGlyphs = rendererParameters.glyphs.Count();
375
376   // Once the text has been shaped and the glyphs created it's possible to replace the font id of those glyphs
377   // that represent an image or an item and create the embedded item layout info.
378   // Note: the position of the embedded item can't be set until the text is laid-out.
379   embeddedItemLayout.Reserve(textModel->mLogicalModel->mEmbeddedItems.Count());
380   for(const auto& item : textModel->mLogicalModel->mEmbeddedItems)
381   {
382     // Get the glyph that matches with the character index.
383     const GlyphIndex glyphIndex = textModel->mVisualModel->mCharactersToGlyph[item.characterIndex];
384     GlyphInfo&       glyph      = rendererParameters.glyphs[glyphIndex];
385
386     glyph.fontId                                                     = 0u;
387     Pixel::Format                                        pixelFormat = Pixel::A8;
388     TextAbstraction::FontClient::EmbeddedItemDescription description = {std::string(item.url, item.urlLength), item.width, item.height, item.colorBlendingMode};
389     glyph.index                                                      = fontClient.CreateEmbeddedItem(description, pixelFormat); // Set here an index to an item.
390
391     if((Pixel::RGBA8888 == pixelFormat) || (Pixel::BGRA8888 == pixelFormat))
392     {
393       rendererParameters.pixelFormat = TextAbstraction::TextRenderer::Parameters::RGBA8888;
394     }
395
396     // If the url is empty the item is going to be added after the text is rendered. It's needed to store the layout here.
397     if(description.url.empty())
398     {
399       EmbeddedItemInfo embeddedInfo =
400         {
401           item.characterIndex,
402           glyphIndex,
403           Vector2::ZERO,
404           Size(static_cast<float>(item.width), static_cast<float>(item.height)),
405           Size(static_cast<float>(item.width), static_cast<float>(item.height)),
406           Degree(0.f),
407           item.colorBlendingMode};
408
409       embeddedItemLayout.PushBack(embeddedInfo);
410     }
411   }
412 }
413
414 void SetColorSegmentation(const RendererParameters& textParameters, InternalDataModel& internalDataModel)
415 {
416   Text::ModelPtr&            textModel    = internalDataModel.textModel;
417   Vector<ColorBlendingMode>& blendingMode = internalDataModel.blendingMode;
418
419   Vector<ColorRun>& colorRuns = textModel->mLogicalModel->mColorRuns; // colors of the text.
420
421   Vector<GlyphIndex>& charactersToGlyph  = textModel->mVisualModel->mCharactersToGlyph;  // Characters to glyphs map.
422   Vector<Length>&     glyphsPerCharacter = textModel->mVisualModel->mGlyphsPerCharacter; // The number of glyphs that are shaped.
423
424   ////////////////////////////////////////////////////////////////////////////////
425   // Set the color runs in glyphs.
426   ////////////////////////////////////////////////////////////////////////////////
427
428   SetColorSegmentationInfo(colorRuns,
429                            charactersToGlyph,
430                            glyphsPerCharacter,
431                            0u,
432                            0u,
433                            internalDataModel.numberOfCharacters,
434                            textModel->mVisualModel->mColors,
435                            textModel->mVisualModel->mColorIndices);
436
437   // Insert the default color at the beginning of the vector.
438   textModel->mVisualModel->mColors.Insert(textModel->mVisualModel->mColors.Begin(), textParameters.textColor);
439
440   // Set how the embedded items are blended with text color.
441   blendingMode.Resize(internalDataModel.numberOfGlyphs, textParameters.isTextColorSet ? ColorBlendingMode::MULTIPLY : ColorBlendingMode::NONE);
442
443   if(!textParameters.isTextColorSet)
444   {
445     // Traverse the color runs.
446     for(const auto& run : colorRuns)
447     {
448       const GlyphIndex     firstGlyph       = textModel->mVisualModel->mCharactersToGlyph[run.characterRun.characterIndex];
449       const CharacterIndex lastCharacter    = run.characterRun.characterIndex + run.characterRun.numberOfCharacters - 1u;
450       const GlyphIndex     lastGlyphPlusOne = textModel->mVisualModel->mCharactersToGlyph[lastCharacter] + textModel->mVisualModel->mGlyphsPerCharacter[lastCharacter];
451
452       for(GlyphIndex index = firstGlyph; index < lastGlyphPlusOne; ++index)
453       {
454         blendingMode[index] = ColorBlendingMode::MULTIPLY;
455       }
456     }
457   }
458
459   // Traverse the embedded items and update the blending mode vector.
460   for(const auto& item : textModel->mLogicalModel->mEmbeddedItems)
461   {
462     const GlyphIndex glyphIndex = textModel->mVisualModel->mCharactersToGlyph[item.characterIndex];
463     blendingMode[glyphIndex]    = item.colorBlendingMode;
464   }
465 }
466
467 void SetEmojiVector(InternalDataModel& internalDataModel)
468 {
469   Vector<bool>&   isEmoji        = internalDataModel.isEmoji;
470   Text::ModelPtr& textModel      = internalDataModel.textModel;
471   const Length    numberOfGlyphs = internalDataModel.numberOfGlyphs;
472
473   const Vector<ScriptRun>& scripts = textModel->mLogicalModel->mScriptRuns; // Charactes's script.
474   ////////////////////////////////////////////////////////////////////////////////
475   // Set the isEmoji Vector
476   ////////////////////////////////////////////////////////////////////////////////
477
478   isEmoji.Resize(numberOfGlyphs, false);
479
480   for(const auto& run : scripts)
481   {
482     if(run.script == TextAbstraction::Script::EMOJI)
483     {
484       const GlyphIndex     firstGlyph       = textModel->mVisualModel->mCharactersToGlyph[run.characterRun.characterIndex];
485       const CharacterIndex lastCharacter    = run.characterRun.characterIndex + run.characterRun.numberOfCharacters - 1u;
486       const GlyphIndex     lastGlyphPlusOne = textModel->mVisualModel->mCharactersToGlyph[lastCharacter] + textModel->mVisualModel->mGlyphsPerCharacter[lastCharacter];
487
488       for(GlyphIndex index = firstGlyph; index < lastGlyphPlusOne; ++index)
489       {
490         isEmoji[index] = true;
491       }
492     }
493   }
494 }
495
496 void Align(const RendererParameters& textParameters, TextAbstraction::TextRenderer::Parameters& rendererParameters, Vector<EmbeddedItemInfo>& embeddedItemLayout, InternalDataModel& internalDataModel, const Size& newLayoutSize)
497 {
498   Text::Layout::Engine& layoutEngine       = internalDataModel.layoutEngine;
499   Text::ModelPtr&       textModel          = internalDataModel.textModel;
500   const Length          numberOfCharacters = internalDataModel.numberOfCharacters;
501   Size&                 textLayoutArea     = internalDataModel.textLayoutArea;
502
503   Vector<LineRun>& lines = textModel->mVisualModel->mLines; // The laid out lines.
504
505   ////////////////////////////////////////////////////////////////////////////////
506   // Align the text.
507   ////////////////////////////////////////////////////////////////////////////////
508
509   HorizontalAlignment::Type horizontalAlignment         = Toolkit::HorizontalAlignment::CENTER;
510   HorizontalAlignment::Type horizontalCircularAlignment = Toolkit::HorizontalAlignment::CENTER;
511   VerticalAlignment::Type   verticalAlignment           = VerticalAlignment::CENTER;
512   CircularAlignment::Type   circularAlignment           = CircularAlignment::BEGIN;
513
514   Layout::Type layout = Layout::SINGLELINE;
515
516   // Sets the alignment
517   Property::Value horizontalAlignmentStr(textParameters.horizontalAlignment);
518   GetHorizontalAlignmentEnumeration(horizontalAlignmentStr, horizontalAlignment);
519   horizontalCircularAlignment = horizontalAlignment;
520
521   Property::Value verticalAlignmentStr(textParameters.verticalAlignment);
522   GetVerticalAlignmentEnumeration(verticalAlignmentStr, verticalAlignment);
523
524   Property::Value circularAlignmentStr(textParameters.circularAlignment);
525   GetCircularAlignmentEnumeration(circularAlignmentStr, circularAlignment);
526
527   Property::Value layoutStr(textParameters.layout);
528   GetLayoutEnumeration(layoutStr, layout);
529
530   // Whether the layout is circular.
531   const bool isCircularTextLayout = (Layout::CIRCULAR == layout);
532   const bool isClockwise          = isCircularTextLayout && (0.f < textParameters.incrementAngle);
533
534   // Convert CircularAlignment to HorizontalAlignment.
535   if(isCircularTextLayout)
536   {
537     switch(circularAlignment)
538     {
539       case CircularAlignment::BEGIN:
540       {
541         horizontalCircularAlignment = Toolkit::HorizontalAlignment::BEGIN;
542         break;
543       }
544       case CircularAlignment::CENTER:
545       {
546         horizontalCircularAlignment = Toolkit::HorizontalAlignment::CENTER;
547         break;
548       }
549       case CircularAlignment::END:
550       {
551         horizontalCircularAlignment = Toolkit::HorizontalAlignment::END;
552         break;
553       }
554     }
555   }
556   textModel->mHorizontalAlignment = isCircularTextLayout ? horizontalCircularAlignment : horizontalAlignment;
557
558   // Retrieve the line of text to know the direction and the width. @todo multi-line
559   const LineRun& line = lines[0u];
560
561   if(isCircularTextLayout)
562   {
563     // Set the circular alignment.
564     rendererParameters.circularLayout = isClockwise ? TextRenderer::Parameters::CLOCKWISE : TextRenderer::Parameters::COUNTER_CLOCKWISE;
565
566     // Update the text's height to be used by the ellipsis code.
567     textLayoutArea.height = newLayoutSize.height;
568
569     // Set the size of the text laid out on a straight horizontal line.
570     rendererParameters.circularWidth  = static_cast<unsigned int>(newLayoutSize.width);
571     rendererParameters.circularHeight = static_cast<unsigned int>(newLayoutSize.height);
572
573     // Calculate the center of the circular text according the horizontal and vertical alingments and the radius.
574     switch(horizontalAlignment)
575     {
576       case HorizontalAlignment::BEGIN:
577       {
578         rendererParameters.centerX = static_cast<int>(textParameters.radius);
579         break;
580       }
581       case HorizontalAlignment::CENTER:
582       {
583         rendererParameters.centerX = static_cast<int>(textParameters.textWidth / 2u);
584         break;
585       }
586       case HorizontalAlignment::END:
587       {
588         rendererParameters.centerX = static_cast<int>(textParameters.textWidth) - static_cast<int>(textParameters.radius);
589         break;
590       }
591     }
592
593     switch(verticalAlignment)
594     {
595       case VerticalAlignment::TOP:
596       {
597         rendererParameters.centerY = static_cast<int>(textParameters.radius);
598         break;
599       }
600       case VerticalAlignment::CENTER:
601       {
602         rendererParameters.centerY = static_cast<int>(textParameters.textHeight / 2u);
603         break;
604       }
605       case VerticalAlignment::BOTTOM:
606       {
607         rendererParameters.centerY = static_cast<int>(textParameters.textHeight) - static_cast<int>(textParameters.radius);
608         break;
609       }
610     }
611
612     // Calculate the beginning angle according with the given horizontal alignment.
613     const bool isRTL = RTL == line.direction;
614
615     CircularAlignment::Type alignment = circularAlignment;
616     if(isRTL)
617     {
618       // Swap the alignment type if the line is right to left.
619       switch(alignment)
620       {
621         case CircularAlignment::BEGIN:
622         {
623           alignment = CircularAlignment::END;
624           break;
625         }
626         case CircularAlignment::CENTER:
627         {
628           // Nothing to do.
629           break;
630         }
631         case CircularAlignment::END:
632         {
633           alignment = CircularAlignment::BEGIN;
634           break;
635         }
636       }
637     }
638
639     float angleOffset = 0.f;
640
641     switch(alignment)
642     {
643       case CircularAlignment::BEGIN:
644       {
645         angleOffset = 0.f;
646         break;
647       }
648       case CircularAlignment::CENTER:
649       {
650         const bool  isNeg     = textParameters.incrementAngle < 0.f;
651         const float textWidth = static_cast<float>(rendererParameters.circularWidth);
652         angleOffset           = (isNeg ? -0.5f : 0.5f) * (textLayoutArea.width - textWidth) / static_cast<float>(rendererParameters.radius);
653         break;
654       }
655       case CircularAlignment::END:
656       {
657         const bool  isNeg     = textParameters.incrementAngle < 0.f;
658         const float textWidth = static_cast<float>(rendererParameters.circularWidth);
659         angleOffset           = (isNeg ? -1.f : 1.f) * (textLayoutArea.width - textWidth) / static_cast<float>(rendererParameters.radius);
660         break;
661       }
662     }
663
664     // Update the beginning angle with the calculated offset.
665     rendererParameters.beginAngle = Radian(Degree(textParameters.beginAngle)) + angleOffset;
666
667     // Set the vertical position of the glyphs.
668     for(auto& position : rendererParameters.positions)
669     {
670       position.y = 0.f;
671     }
672   }
673   else
674   {
675     // Calculate the vertical offset according with the given alignment.
676     float penY = 0.f;
677
678     switch(verticalAlignment)
679     {
680       case VerticalAlignment::TOP:
681       {
682         penY = line.ascender;
683         break;
684       }
685       case VerticalAlignment::CENTER:
686       {
687         penY = line.ascender + 0.5f * (textLayoutArea.height - (line.ascender - line.descender));
688         break;
689       }
690       case VerticalAlignment::BOTTOM:
691       {
692         penY = textLayoutArea.height;
693         break;
694       }
695     }
696
697     // Calculate the horizontal offset according with the given alignment.
698     float alignmentOffset = 0.f;
699     layoutEngine.Align(textLayoutArea,
700                        0u,
701                        numberOfCharacters,
702                        horizontalAlignment,
703                        lines,
704                        alignmentOffset,
705                        Dali::LayoutDirection::LEFT_TO_RIGHT,
706                        false);
707
708     // Update the position of the glyphs with the calculated offsets.
709     for(auto& position : rendererParameters.positions)
710     {
711       position.x += line.alignmentOffset;
712       position.y = penY;
713     }
714   }
715
716   // Cairo adds the bearing to the position of the glyph
717   // that has already been added by the DALi's layout engine,
718   // so it's needed to be removed here.
719   for(unsigned int index = 0u; index < rendererParameters.glyphs.Count(); ++index)
720   {
721     const GlyphInfo& glyph    = rendererParameters.glyphs[index];
722     Vector2&         position = rendererParameters.positions[index];
723
724     position.x -= glyph.xBearing;
725   }
726
727   // Set the position of the embedded items (if there is any).
728   EmbeddedItemInfo* embeddedItemLayoutBuffer = embeddedItemLayout.Begin();
729
730   for(Length index = 0u, endIndex = embeddedItemLayout.Count(); index < endIndex; ++index)
731   {
732     EmbeddedItemInfo& embeddedItem = *(embeddedItemLayoutBuffer + index);
733
734     embeddedItem.position = rendererParameters.positions[embeddedItem.glyphIndex];
735
736     if(isCircularTextLayout)
737     {
738       // Calculate the new position of the embedded item in the circular path.
739
740       // Center of the bitmap.
741       const float halfWidth  = 0.5f * embeddedItem.size.width;
742       const float halfHeight = 0.5f * embeddedItem.size.height;
743       double      centerX    = static_cast<double>(embeddedItem.position.x + halfWidth);
744       double      centerY    = static_cast<double>(embeddedItem.position.y - halfHeight);
745
746       Dali::TextAbstraction::CircularTextParameters circularTextParameters;
747
748       circularTextParameters.radius     = static_cast<double>(rendererParameters.radius);
749       circularTextParameters.invRadius  = 1.0 / circularTextParameters.radius;
750       circularTextParameters.beginAngle = static_cast<double>(-rendererParameters.beginAngle + Dali::Math::PI_2);
751       circularTextParameters.centerX    = 0.5f * static_cast<double>(textParameters.textWidth);
752       circularTextParameters.centerY    = 0.5f * static_cast<double>(textParameters.textHeight);
753
754       // Calculate the rotation angle.
755       float radians = rendererParameters.beginAngle;
756       if(isClockwise)
757       {
758         radians += static_cast<float>(circularTextParameters.invRadius * centerX);
759         radians = -radians;
760       }
761       else
762       {
763         radians -= static_cast<float>(circularTextParameters.invRadius * centerX);
764         radians = -radians + Dali::Math::PI;
765       }
766       embeddedItem.angle = Degree(Radian(radians));
767
768       Dali::TextAbstraction::TransformToArc(circularTextParameters, centerX, centerY);
769
770       // Recalculate the size of the embedded item after the rotation to position it correctly.
771       float width  = embeddedItem.size.width;
772       float height = embeddedItem.size.height;
773
774       // Transform the input angle into the range [0..2PI]
775       radians = fmod(radians, TWO_PI);
776       radians += (radians < 0.f) ? TWO_PI : 0.f;
777
778       // Does the same operations than rotate by shear.
779       if((radians > Math::PI_4) && (radians <= RAD_135))
780       {
781         std::swap(width, height);
782         radians -= Math::PI_2;
783       }
784       else if((radians > RAD_135) && (radians <= RAD_225))
785       {
786         radians -= Math::PI;
787       }
788       else if((radians > RAD_225) && (radians <= RAD_315))
789       {
790         std::swap(width, height);
791         radians -= RAD_270;
792       }
793
794       if(fabs(radians) > Dali::Math::MACHINE_EPSILON_10)
795       {
796         const float angleSinus   = fabs(sin(radians));
797         const float angleCosinus = cos(radians);
798
799         // Calculate the rotated image dimensions.
800         embeddedItem.rotatedSize.height = width * angleSinus + height * angleCosinus;
801         embeddedItem.rotatedSize.width  = height * angleSinus + width * angleCosinus + 1.f;
802       }
803
804       embeddedItem.position.x = floor(static_cast<float>(centerX) - 0.5f * embeddedItem.rotatedSize.width);
805       embeddedItem.position.y = floor(static_cast<float>(centerY) - 0.5f * embeddedItem.rotatedSize.height);
806     }
807     else
808     {
809       embeddedItem.position.y -= embeddedItem.size.height;
810     }
811   }
812 }
813
814 void Ellipsis(const RendererParameters& textParameters, TextAbstraction::TextRenderer::Parameters& rendererParameters, Vector<EmbeddedItemInfo>& embeddedItemLayout, InternalDataModel& internalDataModel)
815 {
816   Text::ModelPtr& textModel  = internalDataModel.textModel;
817   FontClient&     fontClient = internalDataModel.fontClient;
818
819   Vector<LineRun>&        lines                     = textModel->mVisualModel->mLines; // The laid out lines.
820   Vector<bool>&           isEmoji                   = internalDataModel.isEmoji;
821   const Size              textLayoutArea            = internalDataModel.textLayoutArea;
822   const float             characterSpacing          = textModel->mVisualModel->GetCharacterSpacing();
823   float                   calculatedAdvance         = 0.f;
824   Vector<CharacterIndex>& glyphToCharacterMap       = textModel->mVisualModel->mGlyphsToCharacters;
825   const CharacterIndex*   glyphToCharacterMapBuffer = glyphToCharacterMap.Begin();
826   ////////////////////////////////////////////////////////////////////////////////
827   // Ellipsis the text.
828   ////////////////////////////////////////////////////////////////////////////////
829
830   if(textParameters.ellipsisEnabled)
831   {
832     const LineRun& line = lines[0u]; // TODO: multi-line
833
834     if(line.ellipsis)
835     {
836       Length finalNumberOfGlyphs = 0u;
837
838       if((GetLineHeight(line, (lines.Size() == 1))) > textLayoutArea.height)
839       {
840         // The height of the line is bigger than the height of the text area.
841         // Show the ellipsis glyph even if it doesn't fit in the text area.
842         // If no text is rendered then issues are rised and it may be a while
843         // until is find out that the text area is too small.
844
845         // Get the first glyph which is going to be replaced and the ellipsis glyph.
846         GlyphInfo&       glyphInfo     = rendererParameters.glyphs[0u];
847         const GlyphInfo& ellipsisGlyph = fontClient.GetEllipsisGlyph(fontClient.GetPointSize(glyphInfo.fontId));
848
849         // Change the 'x' and 'y' position of the ellipsis glyph.
850         Vector2& position = rendererParameters.positions[0u];
851         position.x        = ellipsisGlyph.xBearing;
852         position.y        = textLayoutArea.height - ellipsisGlyph.yBearing;
853
854         // Replace the glyph by the ellipsis glyph.
855         glyphInfo = ellipsisGlyph;
856
857         // Set the final number of glyphs
858         finalNumberOfGlyphs = 1u;
859       }
860       else
861       {
862         // firstPenX, penY and firstPenSet are used to position the ellipsis glyph if needed.
863         float firstPenX   = 0.f; // Used if rtl text is elided.
864         bool  firstPenSet = false;
865
866         // Add the ellipsis glyph.
867         bool   inserted              = false;
868         float  removedGlypsWidth     = 0.f;
869         Length numberOfRemovedGlyphs = 0u;
870         if(line.glyphRun.numberOfGlyphs > 0u)
871         {
872           GlyphIndex index = line.glyphRun.numberOfGlyphs - 1u;
873
874           GlyphInfo* glyphs         = rendererParameters.glyphs.Begin();
875           Vector2*   glyphPositions = rendererParameters.positions.Begin();
876
877           float penY = 0.f;
878
879           // The ellipsis glyph has to fit in the place where the last glyph(s) is(are) removed.
880           while(!inserted)
881           {
882             const GlyphInfo& glyphToRemove = *(glyphs + index);
883
884             if(0u != glyphToRemove.fontId)
885             {
886               // i.e. The font id of the glyph shaped from the '\n' character is zero.
887
888               // Need to reshape the glyph as the font may be different in size.
889               const GlyphInfo& ellipsisGlyph = fontClient.GetEllipsisGlyph(fontClient.GetPointSize(glyphToRemove.fontId));
890
891               if(!firstPenSet)
892               {
893                 const Vector2& position = *(glyphPositions + index);
894
895                 // Calculates the penY of the current line. It will be used to position the ellipsis glyph.
896                 penY = position.y;
897
898                 // Calculates the first penX which will be used if rtl text is elided.
899                 firstPenX = position.x - glyphToRemove.xBearing;
900                 if(firstPenX < -ellipsisGlyph.xBearing)
901                 {
902                   // Avoids to exceed the bounding box when rtl text is elided.
903                   firstPenX = -ellipsisGlyph.xBearing;
904                 }
905
906                 removedGlypsWidth = -ellipsisGlyph.xBearing;
907
908                 firstPenSet = true;
909               }
910
911               calculatedAdvance = GetCalculatedAdvance(*(textModel->mLogicalModel->mText.Begin() + (*(glyphToCharacterMapBuffer + index))), characterSpacing, glyphToRemove.advance);
912               removedGlypsWidth += std::min(calculatedAdvance, (glyphToRemove.xBearing + glyphToRemove.width));
913
914               // Calculate the width of the ellipsis glyph and check if it fits.
915               const float ellipsisGlyphWidth = ellipsisGlyph.width + ellipsisGlyph.xBearing;
916               if(ellipsisGlyphWidth < removedGlypsWidth)
917               {
918                 GlyphInfo& glyphInfo = *(glyphs + index);
919                 Vector2&   position  = *(glyphPositions + index);
920                 position.x -= (0.f > glyphInfo.xBearing) ? glyphInfo.xBearing : 0.f;
921
922                 // Replace the glyph by the ellipsis glyph.
923                 glyphInfo = ellipsisGlyph;
924
925                 // Update the isEmoji vector
926                 isEmoji[index] = false;
927
928                 // Change the 'x' and 'y' position of the ellipsis glyph.
929
930                 if(position.x > firstPenX)
931                 {
932                   position.x = firstPenX + removedGlypsWidth - ellipsisGlyphWidth;
933                 }
934
935                 position.x += ellipsisGlyph.xBearing;
936                 position.y = penY;
937
938                 inserted = true;
939               }
940             }
941
942             if(!inserted)
943             {
944               if(index > 0u)
945               {
946                 --index;
947               }
948               else
949               {
950                 // No space for the ellipsis.
951                 inserted = true;
952               }
953               ++numberOfRemovedGlyphs;
954             }
955
956             // Set the final number of glyphs
957             finalNumberOfGlyphs = line.glyphRun.numberOfGlyphs - numberOfRemovedGlyphs;
958           }
959         }
960
961         // Resize the number of glyphs/positions
962         rendererParameters.glyphs.Resize(finalNumberOfGlyphs);
963         rendererParameters.positions.Resize(finalNumberOfGlyphs);
964
965         // Remove from the embedded items those exceding the last laid out glyph.
966         embeddedItemLayout.Erase(std::remove_if(embeddedItemLayout.Begin(),
967                                                 embeddedItemLayout.End(),
968                                                 [finalNumberOfGlyphs](const EmbeddedItemInfo& item) {
969                                                   return item.glyphIndex >= finalNumberOfGlyphs;
970                                                 }),
971                                  embeddedItemLayout.End());
972       }
973     }
974   }
975 }
976
977 Size LayoutText(const RendererParameters& textParameters, TextAbstraction::TextRenderer::Parameters& rendererParameters, Vector<EmbeddedItemInfo>& embeddedItemLayout, InternalDataModel& internalDataModel)
978 {
979   ////////////////////////////////////////////////////////////////////////////////
980   // Layout the text.
981   ////////////////////////////////////////////////////////////////////////////////
982   Text::ModelPtr&          textModel               = internalDataModel.textModel;
983   Text::Layout::Engine&    layoutEngine            = internalDataModel.layoutEngine;
984   FontClient&              fontClient              = internalDataModel.fontClient;
985   const Length             numberOfGlyphs          = internalDataModel.numberOfGlyphs;
986   const bool               isTextMirrored          = internalDataModel.isTextMirrored;
987   const Vector<Character>& mirroredUtf32Characters = internalDataModel.mirroredUtf32Characters;
988   const Length             numberOfCharacters      = internalDataModel.numberOfCharacters;
989   const auto               ellipsisPosition        = textModel->mEllipsisPosition;
990
991   Layout::Type layout = Layout::SINGLELINE;
992
993   Property::Value layoutStr(textParameters.layout);
994   GetLayoutEnumeration(layoutStr, layout);
995
996   // Whether the layout is multi-line.
997   const Text::Layout::Engine::Type horizontalLayout = (Layout::MULTILINE == layout) ? Text::Layout::Engine::MULTI_LINE_BOX : Text::Layout::Engine::SINGLE_LINE_BOX;
998   layoutEngine.SetLayout(horizontalLayout); // TODO: multi-line.
999
1000   // Set minimun line size
1001   layoutEngine.SetDefaultLineSize(textParameters.minLineSize);
1002
1003   // Whether the layout is circular.
1004   const bool isCircularTextLayout = (Layout::CIRCULAR == layout);
1005   const bool isClockwise          = isCircularTextLayout && (0.f < textParameters.incrementAngle);
1006
1007   // Calculates the max ascender or the max descender.
1008   // Is used to calculate the radius of the base line of the text.
1009   float maxAscenderDescender = 0.f;
1010   if(isCircularTextLayout)
1011   {
1012     FontId currentFontId = 0u;
1013     for(const auto& glyph : rendererParameters.glyphs)
1014     {
1015       if(currentFontId != glyph.fontId)
1016       {
1017         currentFontId = glyph.fontId;
1018         FontMetrics metrics;
1019         fontClient.GetFontMetrics(currentFontId, metrics);
1020         maxAscenderDescender = std::max(maxAscenderDescender, isClockwise ? metrics.ascender : metrics.descender);
1021       }
1022     }
1023   }
1024   const unsigned int radius = textParameters.radius - static_cast<unsigned int>(maxAscenderDescender);
1025
1026   // Set the layout parameters.
1027   Size textLayoutArea = Size(static_cast<float>(textParameters.textWidth),
1028                              static_cast<float>(textParameters.textHeight));
1029
1030   // padding
1031   Extents padding                  = textParameters.padding;
1032   internalDataModel.textLayoutArea = Size(textLayoutArea.x - (padding.start + padding.end), textLayoutArea.y - (padding.top + padding.bottom));
1033
1034   if(isCircularTextLayout)
1035   {
1036     // In a circular layout, the length of the text area depends on the radius.
1037     rendererParameters.radius              = radius;
1038     internalDataModel.textLayoutArea.width = fabs(Radian(Degree(textParameters.incrementAngle)) * static_cast<float>(rendererParameters.radius));
1039   }
1040   // Resize the vector of positions to have the same size than the vector of glyphs.
1041   rendererParameters.positions.Resize(numberOfGlyphs);
1042
1043   textModel->mLineWrapMode          = Text::LineWrap::WORD;
1044   textModel->mIgnoreSpacesAfterText = false;
1045   Text::Layout::Parameters layoutParameters(internalDataModel.textLayoutArea,
1046                                             textModel);
1047
1048   // Whether the last character is a new paragraph character.
1049   const Vector<Character>& textToShape = isTextMirrored ? mirroredUtf32Characters : textModel->mLogicalModel->mText;
1050   layoutParameters.isLastNewParagraph  = TextAbstraction::IsNewParagraph(textToShape[numberOfCharacters - 1u]);
1051
1052   // The initial glyph and the number of glyphs to layout.
1053   layoutParameters.startGlyphIndex        = 0u;
1054   layoutParameters.numberOfGlyphs         = numberOfGlyphs;
1055   layoutParameters.startLineIndex         = 0u;
1056   layoutParameters.estimatedNumberOfLines = 1u;
1057   layoutParameters.interGlyphExtraAdvance = 0.f;
1058
1059   // Update the visual model.
1060   Size newLayoutSize;
1061   bool isAutoScrollEnabled = false;
1062   layoutEngine.LayoutText(layoutParameters,
1063                           newLayoutSize,
1064                           textParameters.ellipsisEnabled,
1065                           isAutoScrollEnabled,
1066                           ellipsisPosition);
1067
1068   return newLayoutSize;
1069 }
1070
1071 Devel::PixelBuffer RenderText(const RendererParameters& textParameters, TextAbstraction::TextRenderer::Parameters& rendererParameters)
1072 {
1073   ////////////////////////////////////////////////////////////////////////////////
1074   // Render the text.
1075   ////////////////////////////////////////////////////////////////////////////////
1076
1077   rendererParameters.width  = textParameters.textWidth;
1078   rendererParameters.height = textParameters.textHeight;
1079
1080   TextAbstraction::TextRenderer renderer = TextAbstraction::TextRenderer::Get();
1081   return renderer.Render(rendererParameters);
1082 }
1083
1084 Devel::PixelBuffer Render(const RendererParameters& textParameters, Vector<EmbeddedItemInfo>& embeddedItemLayout)
1085 {
1086   if(textParameters.text.empty())
1087   {
1088     Dali::Devel::PixelBuffer pixelBuffer = Dali::Devel::PixelBuffer::New(textParameters.textWidth,
1089                                                                          textParameters.textHeight,
1090                                                                          Dali::Pixel::RGBA8888);
1091
1092     const unsigned int bufferSize = textParameters.textWidth * textParameters.textHeight * Dali::Pixel::GetBytesPerPixel(Dali::Pixel::RGBA8888);
1093     unsigned char*     buffer     = pixelBuffer.GetBuffer();
1094     memset(buffer, 0, bufferSize);
1095
1096     return pixelBuffer;
1097   }
1098
1099   FontClient fontClient = FontClient::Get();
1100   MetricsPtr metrics;
1101   // Use this to access FontClient i.e. to get down-scaled Emoji metrics.
1102   metrics = Metrics::New(fontClient);
1103
1104   Text::ModelPtr    textModel = Text::Model::New();
1105   InternalDataModel internalData(fontClient, metrics, textModel);
1106
1107   TextAbstraction::TextRenderer::Parameters rendererParameters(internalData.textModel->mVisualModel->mGlyphs,
1108                                                                internalData.textModel->mVisualModel->mGlyphPositions,
1109                                                                internalData.textModel->mVisualModel->mColors,
1110                                                                internalData.textModel->mVisualModel->mColorIndices,
1111                                                                internalData.blendingMode,
1112                                                                internalData.isEmoji);
1113
1114   rendererParameters.width       = textParameters.textWidth;
1115   rendererParameters.height      = textParameters.textHeight;
1116   rendererParameters.pixelFormat = TextAbstraction::TextRenderer::Parameters::RGBA8888; // @note: At the moment all textures are generated RGBA8888
1117
1118   ////////////////////////////////////////////////////////////////////////////////
1119   // Process the markup string if the mark-up processor is enabled.
1120   ////////////////////////////////////////////////////////////////////////////////
1121   ShapeTextPreprocess(textParameters, rendererParameters, internalData);
1122
1123   ////////////////////////////////////////////////////////////////////////////////
1124   // Retrieve the glyphs. Text shaping
1125   ////////////////////////////////////////////////////////////////////////////////
1126   ShapeText(rendererParameters, embeddedItemLayout, internalData);
1127
1128   ////////////////////////////////////////////////////////////////////////////////
1129   // Retrieve the glyph's metrics.
1130   ////////////////////////////////////////////////////////////////////////////////
1131
1132   metrics->GetGlyphMetrics(rendererParameters.glyphs.Begin(), internalData.numberOfGlyphs);
1133
1134   ////////////////////////////////////////////////////////////////////////////////
1135   // Set the color runs in glyphs.
1136   ////////////////////////////////////////////////////////////////////////////////
1137   SetColorSegmentation(textParameters, internalData);
1138
1139   ////////////////////////////////////////////////////////////////////////////////
1140   // Set the isEmoji Vector
1141   ////////////////////////////////////////////////////////////////////////////////
1142   SetEmojiVector(internalData);
1143
1144   ////////////////////////////////////////////////////////////////////////////////
1145   // Layout the text
1146   ////////////////////////////////////////////////////////////////////////////////
1147   Size newLayoutSize = LayoutText(textParameters, rendererParameters, embeddedItemLayout, internalData);
1148
1149   ////////////////////////////////////////////////////////////////////////////////
1150   // Align the text.
1151   ////////////////////////////////////////////////////////////////////////////////
1152   Align(textParameters, rendererParameters, embeddedItemLayout, internalData, newLayoutSize);
1153
1154   ////////////////////////////////////////////////////////////////////////////////
1155   // Ellipsis the text.
1156   ////////////////////////////////////////////////////////////////////////////////
1157   Ellipsis(textParameters, rendererParameters, embeddedItemLayout, internalData);
1158
1159   ////////////////////////////////////////////////////////////////////////////////
1160   // Render the text.
1161   ////////////////////////////////////////////////////////////////////////////////
1162   return RenderText(textParameters, rendererParameters);
1163 }
1164
1165 Devel::PixelBuffer CreateShadow(const ShadowParameters& shadowParameters)
1166 {
1167   // The size of the pixel data.
1168   const int width  = static_cast<int>(shadowParameters.input.GetWidth());
1169   const int height = static_cast<int>(shadowParameters.input.GetHeight());
1170
1171   // The shadow's offset.
1172   const int xOffset = static_cast<int>(shadowParameters.offset.x);
1173   const int yOffset = static_cast<int>(shadowParameters.offset.y);
1174
1175   // The size in bytes of the pixel of the input's buffer.
1176   const Pixel::Format inputFormat    = shadowParameters.input.GetPixelFormat();
1177   const unsigned int  inputPixelSize = Pixel::GetBytesPerPixel(inputFormat);
1178   const bool          isA8           = Pixel::A8 == inputFormat;
1179
1180   // Creates the output pixel buffer.
1181   Devel::PixelBuffer outputPixelBuffer = Devel::PixelBuffer::New(width, height, Pixel::RGBA8888);
1182
1183   // Clear the output buffer
1184   unsigned char* outputPixelBufferPtr = outputPixelBuffer.GetBuffer();
1185   memset(outputPixelBufferPtr, 0, width * height * Pixel::GetBytesPerPixel(Pixel::RGBA8888));
1186
1187   // Gets the buffer of the input pixel buffer.
1188   const unsigned char* const inputPixelBuffer = shadowParameters.input.GetBuffer();
1189
1190   float textColor[4u];
1191   if(isA8)
1192   {
1193     memcpy(textColor, shadowParameters.textColor.AsFloat(), 4u * sizeof(float));
1194   }
1195   const float* const shadowColor = shadowParameters.color.AsFloat();
1196
1197   // Traverse the input pixel buffer and write the text on the foreground and the shadow on the background.
1198   for(int rowIndex = 0; rowIndex < height; ++rowIndex)
1199   {
1200     // Calculates the rowIndex to the input pixel buffer for the shadow and whether it's within the boundaries.
1201     const int  yOffsetIndex    = rowIndex - yOffset;
1202     const bool isValidRowIndex = ((yOffsetIndex >= 0) && (yOffsetIndex < height));
1203
1204     const int rows       = rowIndex * width;
1205     const int offsetRows = yOffsetIndex * width;
1206     for(int columnIndex = 0; columnIndex < width; ++columnIndex)
1207     {
1208       // Index to the input buffer to retrieve the alpha value of the foreground text.
1209       const unsigned int index = inputPixelSize * static_cast<unsigned int>(rows + columnIndex);
1210
1211       // Build the index to the input buffer to retrieve the alpha value of the background shadow.
1212       unsigned int shadowIndex        = 0u;
1213       bool         isValidShadowIndex = false;
1214       if(isValidRowIndex)
1215       {
1216         const int xOffsetIndex = columnIndex - xOffset;
1217         isValidShadowIndex     = ((xOffsetIndex >= 0) && (xOffsetIndex < width));
1218
1219         if(isValidShadowIndex)
1220         {
1221           shadowIndex = inputPixelSize * static_cast<unsigned int>(offsetRows + xOffsetIndex);
1222         }
1223       }
1224
1225       // If the input buffer is an alpha mask, retrieve the values for the foreground text and the background shadow.
1226       // If not retrieve the color.
1227       float inputShadowOffsetAlphaValue = 1.f;
1228       float inputAlphaValue             = 1.f;
1229       if(isA8)
1230       {
1231         // Retrieve the alpha value for the shadow.
1232         inputShadowOffsetAlphaValue = isValidShadowIndex ? (static_cast<float>(*(inputPixelBuffer + shadowIndex)) / 255.f) : 0.f;
1233
1234         // Retrieve the alpha value for the text.
1235         inputAlphaValue = static_cast<float>(*(inputPixelBuffer + index)) / 255.f;
1236       }
1237       else
1238       {
1239         // The input buffer is not an alpha mask. Retrieve the color.
1240         textColor[0u]               = TO_FLOAT * static_cast<float>(*(inputPixelBuffer + index + 0u));
1241         textColor[1u]               = TO_FLOAT * static_cast<float>(*(inputPixelBuffer + index + 1u));
1242         textColor[2u]               = TO_FLOAT * static_cast<float>(*(inputPixelBuffer + index + 2u));
1243         textColor[3u]               = TO_FLOAT * static_cast<float>(*(inputPixelBuffer + index + 3u));
1244         inputAlphaValue             = textColor[3u];
1245         inputShadowOffsetAlphaValue = isValidShadowIndex ? TO_FLOAT * static_cast<float>(*(inputPixelBuffer + shadowIndex + 3u)) : 0.f;
1246       }
1247
1248       // Build the output color.
1249       float outputColor[4u];
1250
1251       if(shadowParameters.blendShadow)
1252       {
1253         // Blend the shadow's color with the text's color on top
1254         const float textAlpha   = textColor[3u] * inputAlphaValue;
1255         const float shadowAlpha = shadowColor[3u] * inputShadowOffsetAlphaValue;
1256
1257         // Blends the alpha.
1258         outputColor[3u]              = 1.f - ((1.f - textAlpha) * (1.f - shadowAlpha));
1259         const bool isOutputAlphaZero = outputColor[3u] < Dali::Math::MACHINE_EPSILON_1000;
1260         if(isOutputAlphaZero)
1261         {
1262           std::fill(outputColor, outputColor + 4u, 0.f);
1263         }
1264         else
1265         {
1266           // Blends the RGB components.
1267           float shadowComponent = 0.f;
1268           float textComponent   = 0.f;
1269
1270           shadowComponent = shadowColor[0u] * inputShadowOffsetAlphaValue;
1271           textComponent   = textColor[0u] * inputAlphaValue;
1272           outputColor[0u] = (textComponent * textAlpha / outputColor[3u]) + (shadowComponent * shadowAlpha * (1.f - textAlpha) / outputColor[3u]);
1273
1274           shadowComponent = shadowColor[1u] * inputShadowOffsetAlphaValue;
1275           textComponent   = textColor[1u] * inputAlphaValue;
1276           outputColor[1u] = (textComponent * textAlpha / outputColor[3u]) + (shadowComponent * shadowAlpha * (1.f - textAlpha) / outputColor[3u]);
1277
1278           shadowComponent = shadowColor[2u] * inputShadowOffsetAlphaValue;
1279           textComponent   = textColor[2u] * inputAlphaValue;
1280           outputColor[2u] = (textComponent * textAlpha / outputColor[3u]) + (shadowComponent * shadowAlpha * (1.f - textAlpha) / outputColor[3u]);
1281         }
1282       }
1283       else
1284       {
1285         // No blending!!!
1286         std::fill(outputColor, outputColor + 4u, 0.f);
1287
1288         const float textAlpha   = textColor[3u];
1289         const float shadowAlpha = shadowColor[3u] * inputShadowOffsetAlphaValue;
1290
1291         // Write shadow first.
1292         if(shadowAlpha > Dali::Math::MACHINE_EPSILON_1000)
1293         {
1294           outputColor[0u] = shadowColor[0u] * inputShadowOffsetAlphaValue;
1295           outputColor[1u] = shadowColor[1u] * inputShadowOffsetAlphaValue;
1296           outputColor[2u] = shadowColor[2u] * inputShadowOffsetAlphaValue;
1297           outputColor[3u] = shadowAlpha;
1298         }
1299
1300         // Write character on top.
1301         if(textAlpha > Dali::Math::MACHINE_EPSILON_1000)
1302         {
1303           outputColor[0u] = textColor[0u];
1304           outputColor[1u] = textColor[1u];
1305           outputColor[2u] = textColor[2u];
1306           outputColor[3u] = textAlpha;
1307         }
1308       }
1309
1310       // Write the color into the output pixel buffer.
1311       const unsigned int outputIndex             = 4u * (rows + columnIndex);
1312       *(outputPixelBufferPtr + outputIndex + 0u) = static_cast<unsigned char>(TO_UCHAR * outputColor[0u]);
1313       *(outputPixelBufferPtr + outputIndex + 1u) = static_cast<unsigned char>(TO_UCHAR * outputColor[1u]);
1314       *(outputPixelBufferPtr + outputIndex + 2u) = static_cast<unsigned char>(TO_UCHAR * outputColor[2u]);
1315       *(outputPixelBufferPtr + outputIndex + 3u) = static_cast<unsigned char>(TO_UCHAR * outputColor[3u]);
1316     }
1317   }
1318
1319   // Returns the pixel buffer.
1320   return outputPixelBuffer;
1321 }
1322
1323 Devel::PixelBuffer ConvertToRgba8888(Devel::PixelBuffer pixelBuffer, const Vector4& color, bool multiplyByAlpha)
1324 {
1325   if(Dali::Pixel::A8 != pixelBuffer.GetPixelFormat())
1326   {
1327     // Does nothing.
1328     return pixelBuffer;
1329   }
1330
1331   const unsigned int width          = pixelBuffer.GetWidth();
1332   const unsigned int height         = pixelBuffer.GetHeight();
1333   Devel::PixelBuffer newPixelBuffer = Devel::PixelBuffer::New(width, height, Dali::Pixel::RGBA8888);
1334
1335   unsigned char*             dstBuffer = newPixelBuffer.GetBuffer();
1336   const unsigned char* const srcBuffer = pixelBuffer.GetBuffer();
1337
1338   const unsigned char r = static_cast<unsigned char>(TO_UCHAR * color.r);
1339   const unsigned char g = static_cast<unsigned char>(TO_UCHAR * color.g);
1340   const unsigned char b = static_cast<unsigned char>(TO_UCHAR * color.b);
1341
1342   unsigned char dstColor[4];
1343   for(unsigned int j = 0u; j < height; ++j)
1344   {
1345     const unsigned int lineIndex = j * width;
1346     for(unsigned int i = 0u; i < width; ++i)
1347     {
1348       const unsigned int srcIndex = lineIndex + i;
1349
1350       const float srcAlpha = static_cast<float>(*(srcBuffer + srcIndex));
1351
1352       if(multiplyByAlpha)
1353       {
1354         dstColor[0u] = static_cast<unsigned char>(srcAlpha * color.r);
1355         dstColor[1u] = static_cast<unsigned char>(srcAlpha * color.g);
1356         dstColor[2u] = static_cast<unsigned char>(srcAlpha * color.b);
1357         dstColor[3u] = static_cast<unsigned char>(srcAlpha * color.a);
1358       }
1359       else
1360       {
1361         dstColor[0u] = r;
1362         dstColor[1u] = g;
1363         dstColor[2u] = b;
1364         dstColor[3u] = static_cast<unsigned char>(srcAlpha);
1365       }
1366
1367       const unsigned int dstIndex = srcIndex * 4u;
1368       memcpy(dstBuffer + dstIndex, dstColor, 4u);
1369     }
1370   }
1371
1372   return newPixelBuffer;
1373 }
1374
1375 void UpdateBuffer(Devel::PixelBuffer src, Devel::PixelBuffer dst, unsigned int x, unsigned int y, bool blend)
1376 {
1377   const Dali::Pixel::Format pixelFormat = dst.GetPixelFormat();
1378   if(src.GetPixelFormat() != pixelFormat)
1379   {
1380     DALI_LOG_ERROR("PixelBuffer::SetBuffer. The pixel format of the new data must be the same of the current pixel buffer.");
1381     return;
1382   }
1383
1384   const unsigned int srcWidth  = src.GetWidth();
1385   const unsigned int srcHeight = src.GetHeight();
1386   const unsigned int dstWidth  = dst.GetWidth();
1387   const unsigned int dstHeight = dst.GetHeight();
1388
1389   if((x > dstWidth) ||
1390      (y > dstHeight) ||
1391      (x + srcWidth > dstWidth) ||
1392      (y + srcHeight > dstHeight))
1393   {
1394     DALI_LOG_ERROR("PixelBuffer::SetBuffer. The source pixel buffer is out of the boundaries of the destination pixel buffer.");
1395     return;
1396   }
1397
1398   const unsigned int bytesPerPixel = Dali::Pixel::GetBytesPerPixel(pixelFormat);
1399   // Ignore when pixelFormat is invalid or contain float
1400   if(bytesPerPixel == 0u || bytesPerPixel == 6u || bytesPerPixel == 12u)
1401   {
1402     return;
1403   }
1404   const unsigned int alphaIndex = bytesPerPixel - 1u;
1405
1406   const unsigned char* const srcBuffer = src.GetBuffer();
1407   unsigned char*             dstBuffer = dst.GetBuffer();
1408
1409   if(!blend)
1410   {
1411     const unsigned int currentLineSize = dstWidth * bytesPerPixel;
1412     const unsigned int newLineSize     = srcWidth * bytesPerPixel;
1413     unsigned char*     currentBuffer   = dstBuffer + (y * dstWidth + x) * bytesPerPixel;
1414     for(unsigned int j = 0u; j < srcHeight; ++j)
1415     {
1416       memcpy(currentBuffer + j * currentLineSize, srcBuffer + j * newLineSize, newLineSize);
1417     }
1418   }
1419   else
1420   {
1421     float outputColor[4u];
1422
1423     // Blend the src pixel buffer with the dst pixel buffer as background.
1424     //
1425     //  fgColor, fgAlpha, bgColor, bgAlpha
1426     //
1427     //  alpha = 1 - ( 1 - fgAlpha ) * ( 1 - bgAlpha )
1428     //  color = ( fgColor * fgAlpha / alpha ) + ( bgColor * bgAlpha * ( 1 - fgAlpha ) / alpha )
1429
1430     // Jump till the 'x,y' position
1431     const unsigned int dstWidthBytes = dstWidth * bytesPerPixel;
1432     dstBuffer += (y * dstWidthBytes + x * bytesPerPixel);
1433
1434     for(unsigned int j = 0u; j < srcHeight; ++j)
1435     {
1436       const unsigned int srcLineIndex = j * srcWidth;
1437       for(unsigned int i = 0u; i < srcWidth; ++i)
1438       {
1439         const float srcAlpha = TO_FLOAT * static_cast<float>(*(srcBuffer + bytesPerPixel * (srcLineIndex + i) + alphaIndex));
1440         const float dstAlpha = TO_FLOAT * static_cast<float>(*(dstBuffer + i * bytesPerPixel + alphaIndex));
1441
1442         // Blends the alpha channel.
1443         const float oneMinusSrcAlpha = 1.f - srcAlpha;
1444         outputColor[alphaIndex]      = 1.f - (oneMinusSrcAlpha * (1.f - dstAlpha));
1445
1446         // Blends the RGB channels.
1447         const bool isOutputAlphaZero = outputColor[alphaIndex] < Dali::Math::MACHINE_EPSILON_1000;
1448         if(isOutputAlphaZero)
1449         {
1450           std::fill(outputColor, outputColor + bytesPerPixel, 0.f);
1451         }
1452         else
1453         {
1454           const float srcAlphaOverOutputAlpha                 = srcAlpha / outputColor[alphaIndex];                    // fgAlpha / alpha
1455           const float dstAlphaOneMinusSrcAlphaOverOutputAlpha = dstAlpha * oneMinusSrcAlpha / outputColor[alphaIndex]; // bgAlpha * ( 1 - fgAlpha ) / alpha
1456           for(unsigned int index = 0u; index < alphaIndex; ++index)
1457           {
1458             const float dstComponent = TO_FLOAT * static_cast<float>(*(dstBuffer + i * bytesPerPixel + index)) * dstAlpha;
1459             const float srcComponent = TO_FLOAT * static_cast<float>(*(srcBuffer + bytesPerPixel * (srcLineIndex + i) + index)) * srcAlpha;
1460             outputColor[index]       = (srcComponent * srcAlphaOverOutputAlpha) + (dstComponent * dstAlphaOneMinusSrcAlphaOverOutputAlpha);
1461           }
1462         }
1463
1464         for(unsigned int index = 0u; index < bytesPerPixel; ++index)
1465         {
1466           *(dstBuffer + i * bytesPerPixel + index) = static_cast<unsigned char>(TO_UCHAR * outputColor[index]);
1467         }
1468       }
1469
1470       dstBuffer += dstWidthBytes;
1471     }
1472   }
1473 }
1474
1475 Dali::Property::Array RenderForLastIndex(RendererParameters& textParameters)
1476 {
1477   Property::Array offsetValues;
1478   if(textParameters.text.empty())
1479   {
1480     return offsetValues;
1481   }
1482   FontClient fontClient = FontClient::Get();
1483   MetricsPtr metrics;
1484   metrics = Metrics::New(fontClient);
1485
1486   Text::ModelPtr    textModel = Text::Model::New();
1487   InternalDataModel internalData(fontClient, metrics, textModel);
1488
1489   TextAbstraction::TextRenderer::Parameters rendererParameters(textModel->mVisualModel->mGlyphs,
1490                                                                textModel->mVisualModel->mGlyphPositions,
1491                                                                textModel->mVisualModel->mColors,
1492                                                                textModel->mVisualModel->mColorIndices,
1493                                                                internalData.blendingMode,
1494                                                                internalData.isEmoji);
1495
1496   rendererParameters.width  = textParameters.textWidth;
1497   rendererParameters.height = textParameters.textHeight;
1498
1499   ////////////////////////////////////////////////////////////////////////////////
1500   // Process the markup string if the mark-up processor is enabled.
1501   ////////////////////////////////////////////////////////////////////////////////
1502   ShapeTextPreprocess(textParameters, rendererParameters, internalData);
1503
1504   ////////////////////////////////////////////////////////////////////////////////
1505   // Retrieve the glyphs. Text shaping
1506   ////////////////////////////////////////////////////////////////////////////////
1507   Dali::Vector<Dali::Toolkit::DevelText::EmbeddedItemInfo> embeddedItemLayout;
1508   ShapeText(rendererParameters, embeddedItemLayout, internalData);
1509
1510   ////////////////////////////////////////////////////////////////////////////////
1511   // Retrieve the glyph's metrics.
1512   ////////////////////////////////////////////////////////////////////////////////
1513   metrics->GetGlyphMetrics(rendererParameters.glyphs.Begin(), internalData.numberOfGlyphs);
1514
1515   ////////////////////////////////////////////////////////////////////////////////
1516   // Layout the text
1517   ////////////////////////////////////////////////////////////////////////////////
1518   int boundingBox           = textParameters.textHeight - (textParameters.padding.top + textParameters.padding.bottom);
1519   textParameters.textHeight = MAX_INT; // layout for the entire area.
1520   LayoutText(textParameters, rendererParameters, embeddedItemLayout, internalData);
1521
1522   ////////////////////////////////////////////////////////////////////////////////
1523   // Calculation last character index
1524   ////////////////////////////////////////////////////////////////////////////////
1525   Vector<LineRun>& lines              = internalData.textModel->mVisualModel->mLines;
1526   unsigned int     numberOfLines      = lines.Count();
1527   int              numberOfCharacters = 0;
1528   float            penY               = 0.f;
1529   float            lineSize           = internalData.layoutEngine.GetDefaultLineSize();
1530   float            lineOffset         = 0.f;
1531   bool             isLastLine;
1532   for(unsigned int index = 0u; index < numberOfLines; ++index)
1533   {
1534     const LineRun& line = *(lines.Begin() + index);
1535     numberOfCharacters += line.characterRun.numberOfCharacters;
1536     isLastLine = (index == numberOfLines - 1);
1537
1538     lineOffset = lineSize > 0.f ? lineSize : GetLineHeight(line, isLastLine);
1539     penY += lineOffset;
1540     if((penY + lineOffset) > boundingBox)
1541     {
1542       offsetValues.PushBack(numberOfCharacters);
1543       penY = 0.f;
1544     }
1545   }
1546   if(penY > 0.f)
1547   {
1548     // add remain character index
1549     offsetValues.PushBack(numberOfCharacters);
1550   }
1551
1552   return offsetValues;
1553 }
1554
1555 Dali::Property::Array GetLastCharacterIndex(RendererParameters& textParameters)
1556 {
1557   Dali::Property::Array offsetValues = Toolkit::DevelText::RenderForLastIndex(textParameters);
1558   return offsetValues;
1559 }
1560
1561 } // namespace DevelText
1562
1563 } // namespace Toolkit
1564
1565 } // namespace Dali