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