X-Git-Url: http://review.tizen.org/git/?p=platform%2Fcore%2Fuifw%2Fdali-toolkit.git;a=blobdiff_plain;f=dali-toolkit%2Fdevel-api%2Ftext%2Ftext-utils-devel.cpp;h=e5ad92571454dda2f06e67d6b6c4509cf33dffb4;hp=eaab0e50607132a98fafd85870fcad8d890970e5;hb=37a6580a8a0c9c449947872c5aa62daab57456a5;hpb=124aa28a0cd62b61142779066432cd29195c42ce diff --git a/dali-toolkit/devel-api/text/text-utils-devel.cpp b/dali-toolkit/devel-api/text/text-utils-devel.cpp index eaab0e5..e5ad925 100755 --- a/dali-toolkit/devel-api/text/text-utils-devel.cpp +++ b/dali-toolkit/devel-api/text/text-utils-devel.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019 Samsung Electronics Co., Ltd. + * Copyright (c) 2020 Samsung Electronics Co., Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,12 +19,13 @@ #include // EXTERNAL INCLUDES +#include #include -#include #include +#include #include -#include #include +#include // INTERNAL INCLUDES #include @@ -32,18 +33,16 @@ #include #include #include -#include #include #include #include #include #include #include -#include +#include namespace Dali { - using namespace TextAbstraction; namespace Toolkit @@ -52,10 +51,8 @@ using namespace Text; namespace DevelText { - namespace Layout { - /** * @brief The text's layout. */ @@ -70,7 +67,6 @@ enum Type namespace CircularAlignment { - /** * @brief The enumerations for the circular alignment. */ @@ -84,26 +80,58 @@ enum Type } // namespace CircularAlignment const float TO_POINT_26_DOT_6 = 64.f; -const float TO_FLOAT = 1.f / 255.f; -const float TO_UCHAR = 255.f; -const bool RTL = true; -const float TWO_PI = 2.f * Dali::Math::PI; ///< 360 degrees in radians -const float RAD_135 = Math::PI_2 + Math::PI_4; ///< 135 degrees in radians; -const float RAD_225 = RAD_135 + Math::PI_2; ///< 225 degrees in radians; -const float RAD_270 = 3.f * Math::PI_2; ///< 270 degrees in radians; -const float RAD_315 = RAD_225 + Math::PI_2; ///< 315 degrees in radians; - -DALI_ENUM_TO_STRING_TABLE_BEGIN( LAYOUT_TYPE ) -DALI_ENUM_TO_STRING_WITH_SCOPE( DevelText::Layout, SINGLELINE ) -DALI_ENUM_TO_STRING_WITH_SCOPE( DevelText::Layout, MULTILINE ) -DALI_ENUM_TO_STRING_WITH_SCOPE( DevelText::Layout, CIRCULAR ) -DALI_ENUM_TO_STRING_TABLE_END( LAYOUT_TYPE ) - -DALI_ENUM_TO_STRING_TABLE_BEGIN( CIRCULAR_ALIGNMENT_TYPE ) -DALI_ENUM_TO_STRING_WITH_SCOPE( DevelText::CircularAlignment, BEGIN ) -DALI_ENUM_TO_STRING_WITH_SCOPE( DevelText::CircularAlignment, CENTER ) -DALI_ENUM_TO_STRING_WITH_SCOPE( DevelText::CircularAlignment, END ) -DALI_ENUM_TO_STRING_TABLE_END( CIRCULAR_ALIGNMENT_TYPE ) +const float TO_FLOAT = 1.f / 255.f; +const float TO_UCHAR = 255.f; +const bool RTL = true; +const float TWO_PI = 2.f * Dali::Math::PI; ///< 360 degrees in radians +const float RAD_135 = Math::PI_2 + Math::PI_4; ///< 135 degrees in radians; +const float RAD_225 = RAD_135 + Math::PI_2; ///< 225 degrees in radians; +const float RAD_270 = 3.f * Math::PI_2; ///< 270 degrees in radians; +const float RAD_315 = RAD_225 + Math::PI_2; ///< 315 degrees in radians; +const float MAX_INT = std::numeric_limits::max(); + +DALI_ENUM_TO_STRING_TABLE_BEGIN(LAYOUT_TYPE) + DALI_ENUM_TO_STRING_WITH_SCOPE(DevelText::Layout, SINGLELINE) + DALI_ENUM_TO_STRING_WITH_SCOPE(DevelText::Layout, MULTILINE) + DALI_ENUM_TO_STRING_WITH_SCOPE(DevelText::Layout, CIRCULAR) +DALI_ENUM_TO_STRING_TABLE_END(LAYOUT_TYPE) + +DALI_ENUM_TO_STRING_TABLE_BEGIN(CIRCULAR_ALIGNMENT_TYPE) + DALI_ENUM_TO_STRING_WITH_SCOPE(DevelText::CircularAlignment, BEGIN) + DALI_ENUM_TO_STRING_WITH_SCOPE(DevelText::CircularAlignment, CENTER) + DALI_ENUM_TO_STRING_WITH_SCOPE(DevelText::CircularAlignment, END) +DALI_ENUM_TO_STRING_TABLE_END(CIRCULAR_ALIGNMENT_TYPE) + +struct InternalDataModel +{ + InternalDataModel(FontClient& fontClient, + MetricsPtr metrics, + Text::ModelPtr textModel) + : fontClient(fontClient), + metrics(metrics), + textModel(textModel), + numberOfCharacters{0u}, + isTextMirrored{false}, + numberOfGlyphs{0u} + { + layoutEngine.SetMetrics(metrics); + } + + FontClient& fontClient; + MetricsPtr metrics; + Text::Layout::Engine layoutEngine; ///< The layout engine. + Text::ModelPtr textModel; ///< Pointer to the text's model. + Vector blendingMode; ///< How embedded items and bitmap font glyphs are blended with color text. + Vector isEmoji; ///< Whether the glyph is an emoji. + + Vector mirroredUtf32Characters; // The utf32Characters Characters but mirrored if there are RTL text. + + Length numberOfCharacters; // The number of characters (not glyphs!). + bool isTextMirrored; // Whether the text has been mirrored. + + Length numberOfGlyphs; + Size textLayoutArea; +}; bool GetLayoutEnumeration(const Property::Value& propertyValue, DevelText::Layout::Type& layout) { @@ -115,83 +143,38 @@ bool GetCircularAlignmentEnumeration(const Property::Value& propertyValue, Devel return Scripting::GetEnumerationProperty(propertyValue, CIRCULAR_ALIGNMENT_TYPE_TABLE, CIRCULAR_ALIGNMENT_TYPE_TABLE_COUNT, circularAlignment); } -Devel::PixelBuffer Render( const RendererParameters& textParameters, Vector& embeddedItemLayout ) +void ShapeTextPreprocess(const RendererParameters& textParameters, TextAbstraction::TextRenderer::Parameters& rendererParameters, InternalDataModel& internalDataModel) { - if( textParameters.text.empty() ) - { - Dali::Devel::PixelBuffer pixelBuffer = Dali::Devel::PixelBuffer::New( textParameters.textWidth, - textParameters.textHeight, - Dali::Pixel::RGBA8888 ); - - const unsigned int bufferSize = textParameters.textWidth * textParameters.textHeight * Dali::Pixel::GetBytesPerPixel(Dali::Pixel::RGBA8888); - unsigned char* buffer = pixelBuffer.GetBuffer(); - memset(buffer, 0, bufferSize); - - return pixelBuffer; - } - MultilanguageSupport multilanguageSupport = MultilanguageSupport::Get(); - FontClient fontClient = FontClient::Get(); - MetricsPtr metrics; - Text::Layout::Engine layoutEngine; ///< The layout engine. - LogicalModelPtr logicalModel = LogicalModel::New(); ///< Pointer to the logical model. - VisualModelPtr visualModel = VisualModel::New(); ///< Pointer to the visual model. - Vector blendingMode; ///< How embedded items and bitmap font glyphs are blended with color text. - Vector isEmoji; ///< Whether the glyph is an emoji. - - // Use this to access FontClient i.e. to get down-scaled Emoji metrics. - metrics = Metrics::New( fontClient ); - layoutEngine.SetMetrics( metrics ); - - TextAbstraction::TextRenderer::Parameters rendererParameters( visualModel->mGlyphs, - visualModel->mGlyphPositions, - visualModel->mColors, - visualModel->mColorIndices, - blendingMode, - isEmoji ); - - rendererParameters.width = textParameters.textWidth; - rendererParameters.height = textParameters.textHeight; - rendererParameters.pixelFormat = TextAbstraction::TextRenderer::Parameters::RGBA8888; // @note: At the moment all textures are generated RGBA8888 - - Vector& utf32Characters = logicalModel->mText; // Characters encoded in utf32. - Vector mirroredUtf32Characters; // The utf32Characters Characters but mirrored if there are RTL text. - Vector& lineBreakInfo = logicalModel->mLineBreakInfo; // The line break info. - Vector& scripts = logicalModel->mScriptRuns; // Charactes's script. - Vector& fontDescriptionRuns = logicalModel->mFontDescriptionRuns; // Desired font descriptions. - Vector& validFonts = logicalModel->mFontRuns; // Validated fonts. - Vector& bidirectionalInfo = logicalModel->mBidirectionalParagraphInfo; // The bidirectional info per paragraph. - Vector& bidirectionalLineInfo = logicalModel->mBidirectionalLineInfo; // The bidirectional info per line. - Vector& directions = logicalModel->mCharacterDirections; // Character's directions. - Vector& colorRuns = logicalModel->mColorRuns; // colors of the text. - - Vector& glyphsToCharacters = visualModel->mGlyphsToCharacters; // Glyphs to character map. - Vector& charactersToGlyph = visualModel->mCharactersToGlyph; // Characters to glyphs map. - Vector& charactersPerGlyph = visualModel->mCharactersPerGlyph; // Number of characters per glyph. - Vector& glyphsPerCharacter = visualModel->mGlyphsPerCharacter; // The number of glyphs that are shaped. - Vector& lines = visualModel->mLines; // The laid out lines. - - Vector newParagraphGlyphs; // Glyphs for the new paragraph characters. + const uint8_t* utf8 = NULL; // pointer to the first character of the text (encoded in utf8) + Length textSize = 0u; // The length of the utf8 string. + + Length& numberOfCharacters = internalDataModel.numberOfCharacters; + Vector& mirroredUtf32Characters = internalDataModel.mirroredUtf32Characters; + Text::ModelPtr& textModel = internalDataModel.textModel; + + Vector& utf32Characters = textModel->mLogicalModel->mText; // Characters encoded in utf32. + Vector& lineBreakInfo = textModel->mLogicalModel->mLineBreakInfo; // The line break info. + Vector& scripts = textModel->mLogicalModel->mScriptRuns; // Charactes's script. + Vector& fontDescriptionRuns = textModel->mLogicalModel->mFontDescriptionRuns; // Desired font descriptions. + Vector& validFonts = textModel->mLogicalModel->mFontRuns; // Validated fonts. + Vector& bidirectionalInfo = textModel->mLogicalModel->mBidirectionalParagraphInfo; // The bidirectional info per paragraph. + Vector& directions = textModel->mLogicalModel->mCharacterDirections; // Character's directions. + Vector& colorRuns = textModel->mLogicalModel->mColorRuns; // colors of the text. // the default font's description. FontDescription defaultFontDescription; PointSize26Dot6 defaultPointSize = FontClient::DEFAULT_POINT_SIZE; - Length numberOfCharacters = 0u; // The number of characters (not glyphs!). - bool isTextMirrored = false; // Whether the text has been mirrored. - - const uint8_t* utf8 = NULL; // pointer to the first character of the text (encoded in utf8) - Length textSize = 0u; // The length of the utf8 string. - //////////////////////////////////////////////////////////////////////////////// // Process the markup string if the mark-up processor is enabled. //////////////////////////////////////////////////////////////////////////////// - MarkupProcessData markupProcessData( colorRuns, - fontDescriptionRuns, - logicalModel->mEmbeddedItems ); + MarkupProcessData markupProcessData(colorRuns, + fontDescriptionRuns, + textModel->mLogicalModel->mEmbeddedItems); - if (textParameters.markupEnabled) + if(textParameters.markupEnabled) { ProcessMarkupString(textParameters.text, markupProcessData); textSize = markupProcessData.markupProcessedText.size(); @@ -215,34 +198,34 @@ Devel::PixelBuffer Render( const RendererParameters& textParameters, Vector( textParameters.fontSize * TO_POINT_26_DOT_6 ); + fontDescriptionRun.size = static_cast(textParameters.fontSize * TO_POINT_26_DOT_6); } - fontDescriptionRuns.PushBack( fontDescriptionRun ); + fontDescriptionRuns.PushBack(fontDescriptionRun); // Validates the fonts. If there is a character with no assigned font it sets a default one. // After this call, fonts are validated. - multilanguageSupport.ValidateFonts( utf32Characters, - scripts, - fontDescriptionRuns, - defaultFontDescription, - defaultPointSize, - 0u, - numberOfCharacters, - validFonts ); + multilanguageSupport.ValidateFonts(utf32Characters, + scripts, + fontDescriptionRuns, + defaultFontDescription, + defaultPointSize, + 0u, + numberOfCharacters, + validFonts); //////////////////////////////////////////////////////////////////////////////// // Retrieve the Bidirectional info. //////////////////////////////////////////////////////////////////////////////// - bidirectionalInfo.Reserve( 1u ); + bidirectionalInfo.Reserve(1u); - SetBidirectionalInfo( utf32Characters, - scripts, - lineBreakInfo, - 0u, - numberOfCharacters, - bidirectionalInfo ); + SetBidirectionalInfo(utf32Characters, + scripts, + lineBreakInfo, + 0u, + numberOfCharacters, + bidirectionalInfo); const bool hasBidirectionalText = 0u != bidirectionalInfo.Count(); - if( hasBidirectionalText ) + if(hasBidirectionalText) { // Only set the character directions if there is right to left characters. - GetCharactersDirection( bidirectionalInfo, - numberOfCharacters, - 0u, - numberOfCharacters, - directions ); + GetCharactersDirection(bidirectionalInfo, + numberOfCharacters, + 0u, + numberOfCharacters, + directions); // This paragraph has right to left text. Some characters may need to be mirrored. // TODO: consider if the mirrored string can be stored as well. - isTextMirrored = GetMirroredText( utf32Characters, - directions, - bidirectionalInfo, - 0u, - numberOfCharacters, - mirroredUtf32Characters ); + internalDataModel.isTextMirrored = GetMirroredText(utf32Characters, + directions, + bidirectionalInfo, + 0u, + numberOfCharacters, + mirroredUtf32Characters); } +} + +void ShapeText(TextAbstraction::TextRenderer::Parameters& rendererParameters, Vector& embeddedItemLayout, InternalDataModel& internalDataModel) +{ + Vector newParagraphGlyphs; // Glyphs for the new paragraph characters. + const Length numberOfCharacters = internalDataModel.numberOfCharacters; + const bool isTextMirrored = internalDataModel.isTextMirrored; + Text::ModelPtr& textModel = internalDataModel.textModel; + const Vector& mirroredUtf32Characters = internalDataModel.mirroredUtf32Characters; + FontClient& fontClient = internalDataModel.fontClient; + const Vector& utf32Characters = textModel->mLogicalModel->mText; // Characters encoded in utf32. + const Vector& lineBreakInfo = textModel->mLogicalModel->mLineBreakInfo; // The line break info. + const Vector& scripts = textModel->mLogicalModel->mScriptRuns; // Charactes's script. + const Vector& validFonts = textModel->mLogicalModel->mFontRuns; // Validated fonts. + + Vector& glyphsToCharacters = textModel->mVisualModel->mGlyphsToCharacters; // Glyphs to character map. + Vector& charactersPerGlyph = textModel->mVisualModel->mCharactersPerGlyph; // Number of characters per glyph. //////////////////////////////////////////////////////////////////////////////// // Retrieve the glyphs. Text shaping @@ -346,100 +346,104 @@ Devel::PixelBuffer Render( const RendererParameters& textParameters, Vector& textToShape = isTextMirrored ? mirroredUtf32Characters : utf32Characters; - newParagraphGlyphs.Reserve( 1u ); + newParagraphGlyphs.Reserve(1u); // Shapes the text. - ShapeText( textToShape, - lineBreakInfo, - scripts, - validFonts, - 0u, - 0u, - numberOfCharacters, - rendererParameters.glyphs, - glyphsToCharacters, - charactersPerGlyph, - newParagraphGlyphs ); + ShapeText(textToShape, + lineBreakInfo, + scripts, + validFonts, + 0u, + 0u, + numberOfCharacters, + rendererParameters.glyphs, + glyphsToCharacters, + charactersPerGlyph, + newParagraphGlyphs); // Create the 'number of glyphs' per character and the glyph to character conversion tables. - visualModel->CreateGlyphsPerCharacterTable( 0u, 0u, numberOfCharacters ); - visualModel->CreateCharacterToGlyphTable( 0u, 0u, numberOfCharacters ); + textModel->mVisualModel->CreateGlyphsPerCharacterTable(0u, 0u, numberOfCharacters); + textModel->mVisualModel->CreateCharacterToGlyphTable(0u, 0u, numberOfCharacters); - const Length numberOfGlyphs = rendererParameters.glyphs.Count(); + internalDataModel.numberOfGlyphs = rendererParameters.glyphs.Count(); // Once the text has been shaped and the glyphs created it's possible to replace the font id of those glyphs // that represent an image or an item and create the embedded item layout info. // Note: the position of the embedded item can't be set until the text is laid-out. - embeddedItemLayout.Reserve( logicalModel->mEmbeddedItems.Count() ); - for( const auto& item : logicalModel->mEmbeddedItems ) + embeddedItemLayout.Reserve(textModel->mLogicalModel->mEmbeddedItems.Count()); + for(const auto& item : textModel->mLogicalModel->mEmbeddedItems) { // Get the glyph that matches with the character index. - const GlyphIndex glyphIndex = visualModel->mCharactersToGlyph[item.characterIndex]; - GlyphInfo& glyph = rendererParameters.glyphs[glyphIndex]; + const GlyphIndex glyphIndex = textModel->mVisualModel->mCharactersToGlyph[item.characterIndex]; + GlyphInfo& glyph = rendererParameters.glyphs[glyphIndex]; - glyph.fontId = 0u; - Pixel::Format pixelFormat = Pixel::A8; - TextAbstraction::FontClient::EmbeddedItemDescription description = { std::string( item.url, item.urlLength ), item.width, item.height, item.colorBlendingMode }; - glyph.index = fontClient.CreateEmbeddedItem( description, pixelFormat ); // Set here an index to an item. + glyph.fontId = 0u; + Pixel::Format pixelFormat = Pixel::A8; + TextAbstraction::FontClient::EmbeddedItemDescription description = {std::string(item.url, item.urlLength), item.width, item.height, item.colorBlendingMode}; + glyph.index = fontClient.CreateEmbeddedItem(description, pixelFormat); // Set here an index to an item. - if( ( Pixel::RGBA8888 == pixelFormat ) || ( Pixel::BGRA8888 == pixelFormat ) ) + if((Pixel::RGBA8888 == pixelFormat) || (Pixel::BGRA8888 == pixelFormat)) { rendererParameters.pixelFormat = TextAbstraction::TextRenderer::Parameters::RGBA8888; } // 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. - if( description.url.empty() ) + if(description.url.empty()) { EmbeddedItemInfo embeddedInfo = - { - item.characterIndex, - glyphIndex, - Vector2::ZERO, - Size( static_cast( item.width ), static_cast( item.height ) ), - Size( static_cast( item.width ), static_cast( item.height ) ), - Degree( 0.f ), - item.colorBlendingMode - }; - - embeddedItemLayout.PushBack( embeddedInfo ); + { + item.characterIndex, + glyphIndex, + Vector2::ZERO, + Size(static_cast(item.width), static_cast(item.height)), + Size(static_cast(item.width), static_cast(item.height)), + Degree(0.f), + item.colorBlendingMode}; + + embeddedItemLayout.PushBack(embeddedInfo); } } +} - //////////////////////////////////////////////////////////////////////////////// - // Retrieve the glyph's metrics. - //////////////////////////////////////////////////////////////////////////////// +void SetColorSegmentation(const RendererParameters& textParameters, InternalDataModel& internalDataModel) +{ + Text::ModelPtr& textModel = internalDataModel.textModel; + Vector& blendingMode = internalDataModel.blendingMode; - metrics->GetGlyphMetrics( rendererParameters.glyphs.Begin(), numberOfGlyphs ); + Vector& colorRuns = textModel->mLogicalModel->mColorRuns; // colors of the text. + + Vector& charactersToGlyph = textModel->mVisualModel->mCharactersToGlyph; // Characters to glyphs map. + Vector& glyphsPerCharacter = textModel->mVisualModel->mGlyphsPerCharacter; // The number of glyphs that are shaped. //////////////////////////////////////////////////////////////////////////////// // Set the color runs in glyphs. //////////////////////////////////////////////////////////////////////////////// - SetColorSegmentationInfo( colorRuns, - charactersToGlyph, - glyphsPerCharacter, - 0u, - 0u, - numberOfCharacters, - visualModel->mColors, - visualModel->mColorIndices ); + SetColorSegmentationInfo(colorRuns, + charactersToGlyph, + glyphsPerCharacter, + 0u, + 0u, + internalDataModel.numberOfCharacters, + textModel->mVisualModel->mColors, + textModel->mVisualModel->mColorIndices); // Insert the default color at the beginning of the vector. - visualModel->mColors.Insert( visualModel->mColors.Begin(),textParameters.textColor ); + textModel->mVisualModel->mColors.Insert(textModel->mVisualModel->mColors.Begin(), textParameters.textColor); // Set how the embedded items are blended with text color. - blendingMode.Resize( numberOfGlyphs, textParameters.isTextColorSet ? ColorBlendingMode::MULTIPLY : ColorBlendingMode::NONE ); + blendingMode.Resize(internalDataModel.numberOfGlyphs, textParameters.isTextColorSet ? ColorBlendingMode::MULTIPLY : ColorBlendingMode::NONE); - if( !textParameters.isTextColorSet ) + if(!textParameters.isTextColorSet) { // Traverse the color runs. - for( const auto& run : colorRuns ) + for(const auto& run : colorRuns) { - const GlyphIndex firstGlyph = visualModel->mCharactersToGlyph[run.characterRun.characterIndex]; - const CharacterIndex lastCharacter = run.characterRun.characterIndex + run.characterRun.numberOfCharacters - 1u; - const GlyphIndex lastGlyphPlusOne = visualModel->mCharactersToGlyph[lastCharacter] + visualModel->mGlyphsPerCharacter[lastCharacter]; + const GlyphIndex firstGlyph = textModel->mVisualModel->mCharactersToGlyph[run.characterRun.characterIndex]; + const CharacterIndex lastCharacter = run.characterRun.characterIndex + run.characterRun.numberOfCharacters - 1u; + const GlyphIndex lastGlyphPlusOne = textModel->mVisualModel->mCharactersToGlyph[lastCharacter] + textModel->mVisualModel->mGlyphsPerCharacter[lastCharacter]; - for( GlyphIndex index = firstGlyph; index < lastGlyphPlusOne; ++index ) + for(GlyphIndex index = firstGlyph; index < lastGlyphPlusOne; ++index) { blendingMode[index] = ColorBlendingMode::MULTIPLY; } @@ -447,89 +451,84 @@ Devel::PixelBuffer Render( const RendererParameters& textParameters, VectormEmbeddedItems ) + for(const auto& item : textModel->mLogicalModel->mEmbeddedItems) { - const GlyphIndex glyphIndex = visualModel->mCharactersToGlyph[item.characterIndex]; - blendingMode[glyphIndex] = item.colorBlendingMode; + const GlyphIndex glyphIndex = textModel->mVisualModel->mCharactersToGlyph[item.characterIndex]; + blendingMode[glyphIndex] = item.colorBlendingMode; } +} +void SetEmojiVector(InternalDataModel& internalDataModel) +{ + Vector& isEmoji = internalDataModel.isEmoji; + Text::ModelPtr& textModel = internalDataModel.textModel; + const Length numberOfGlyphs = internalDataModel.numberOfGlyphs; + + const Vector& scripts = textModel->mLogicalModel->mScriptRuns; // Charactes's script. //////////////////////////////////////////////////////////////////////////////// // Set the isEmoji Vector //////////////////////////////////////////////////////////////////////////////// - isEmoji.Resize( numberOfGlyphs, false ); + isEmoji.Resize(numberOfGlyphs, false); - for( const auto& run : scripts ) + for(const auto& run : scripts) { - if( run.script == TextAbstraction::Script::EMOJI ) + if(run.script == TextAbstraction::Script::EMOJI) { - const GlyphIndex firstGlyph = visualModel->mCharactersToGlyph[run.characterRun.characterIndex]; - const CharacterIndex lastCharacter = run.characterRun.characterIndex + run.characterRun.numberOfCharacters - 1u; - const GlyphIndex lastGlyphPlusOne = visualModel->mCharactersToGlyph[lastCharacter] + visualModel->mGlyphsPerCharacter[lastCharacter]; + const GlyphIndex firstGlyph = textModel->mVisualModel->mCharactersToGlyph[run.characterRun.characterIndex]; + const CharacterIndex lastCharacter = run.characterRun.characterIndex + run.characterRun.numberOfCharacters - 1u; + const GlyphIndex lastGlyphPlusOne = textModel->mVisualModel->mCharactersToGlyph[lastCharacter] + textModel->mVisualModel->mGlyphsPerCharacter[lastCharacter]; - for( GlyphIndex index = firstGlyph; index < lastGlyphPlusOne; ++index ) + for(GlyphIndex index = firstGlyph; index < lastGlyphPlusOne; ++index) { isEmoji[index] = true; } } } +} + +void Align(const RendererParameters& textParameters, TextAbstraction::TextRenderer::Parameters& rendererParameters, Vector& embeddedItemLayout, InternalDataModel& internalDataModel, const Size& newLayoutSize) +{ + Text::Layout::Engine& layoutEngine = internalDataModel.layoutEngine; + Text::ModelPtr& textModel = internalDataModel.textModel; + const Length numberOfCharacters = internalDataModel.numberOfCharacters; + Size& textLayoutArea = internalDataModel.textLayoutArea; + + Vector& lines = textModel->mVisualModel->mLines; // The laid out lines. //////////////////////////////////////////////////////////////////////////////// - // Layout the text. + // Align the text. //////////////////////////////////////////////////////////////////////////////// - // Sets the alignment - HorizontalAlignment::Type horizontalAlignment = Toolkit::HorizontalAlignment::CENTER; + HorizontalAlignment::Type horizontalAlignment = Toolkit::HorizontalAlignment::CENTER; HorizontalAlignment::Type horizontalCircularAlignment = Toolkit::HorizontalAlignment::CENTER; - VerticalAlignment::Type verticalAlignment = VerticalAlignment::CENTER; + VerticalAlignment::Type verticalAlignment = VerticalAlignment::CENTER; + CircularAlignment::Type circularAlignment = CircularAlignment::BEGIN; + Layout::Type layout = Layout::SINGLELINE; - CircularAlignment::Type circularAlignment = CircularAlignment::BEGIN; - Property::Value horizontalAlignmentStr( textParameters.horizontalAlignment ); - GetHorizontalAlignmentEnumeration( horizontalAlignmentStr, horizontalAlignment ); + // Sets the alignment + Property::Value horizontalAlignmentStr(textParameters.horizontalAlignment); + GetHorizontalAlignmentEnumeration(horizontalAlignmentStr, horizontalAlignment); horizontalCircularAlignment = horizontalAlignment; - Property::Value verticalAlignmentStr( textParameters.verticalAlignment ); - GetVerticalAlignmentEnumeration( verticalAlignmentStr, verticalAlignment ); - - Property::Value layoutStr( textParameters.layout ); - GetLayoutEnumeration( layoutStr, layout ); + Property::Value verticalAlignmentStr(textParameters.verticalAlignment); + GetVerticalAlignmentEnumeration(verticalAlignmentStr, verticalAlignment); - Property::Value circularAlignmentStr( textParameters.circularAlignment ); - GetCircularAlignmentEnumeration( circularAlignmentStr, circularAlignment ); - - // Whether the layout is multi-line. - const Text::Layout::Engine::Type horizontalLayout = ( Layout::MULTILINE == layout ) ? Text::Layout::Engine::MULTI_LINE_BOX : Text::Layout::Engine::SINGLE_LINE_BOX; - layoutEngine.SetLayout( horizontalLayout ); // TODO: multi-line. + Property::Value circularAlignmentStr(textParameters.circularAlignment); + GetCircularAlignmentEnumeration(circularAlignmentStr, circularAlignment); + Property::Value layoutStr(textParameters.layout); + GetLayoutEnumeration(layoutStr, layout); // Whether the layout is circular. const bool isCircularTextLayout = (Layout::CIRCULAR == layout); - const bool isClockwise = isCircularTextLayout && ( 0.f < textParameters.incrementAngle ); - - // Calculates the max ascender or the max descender. - // Is used to calculate the radius of the base line of the text. - float maxAscenderDescender = 0.f; - if( isCircularTextLayout ) - { - FontId currentFontId = 0u; - for( const auto& glyph : rendererParameters.glyphs ) - { - if( currentFontId != glyph.fontId ) - { - currentFontId = glyph.fontId; - FontMetrics metrics; - fontClient.GetFontMetrics(currentFontId, metrics); - maxAscenderDescender = std::max( maxAscenderDescender, isClockwise ? metrics.ascender : metrics.descender ); - } - } - } - const unsigned int radius = textParameters.radius - static_cast( maxAscenderDescender ); + const bool isClockwise = isCircularTextLayout && (0.f < textParameters.incrementAngle); // Convert CircularAlignment to HorizontalAlignment. - if( isCircularTextLayout ) + if(isCircularTextLayout) { - switch( circularAlignment ) + switch(circularAlignment) { case CircularAlignment::BEGIN: { @@ -548,92 +547,12 @@ Devel::PixelBuffer Render( const RendererParameters& textParameters, Vector( textParameters.textWidth ), - static_cast( textParameters.textHeight ) ); - - if( isCircularTextLayout ) - { - // In a circular layout, the length of the text area depends on the radius. - rendererParameters.radius = radius; - textLayoutArea.width = fabs( Radian( Degree( textParameters.incrementAngle ) ) * static_cast( rendererParameters.radius ) ); - } - - Text::Layout::Parameters layoutParameters( textLayoutArea, - textToShape.Begin(), - lineBreakInfo.Begin(), - nullptr, - ( 0u != directions.Count() ) ? directions.Begin() : nullptr, - rendererParameters.glyphs.Begin(), - glyphsToCharacters.Begin(), - charactersPerGlyph.Begin(), - charactersToGlyph.Begin(), - glyphsPerCharacter.Begin(), - numberOfGlyphs, - isCircularTextLayout ? horizontalCircularAlignment : horizontalAlignment, - LineWrap::WORD, - 0.f, - false, - false ); // Outline's width - - // Resize the vector of positions to have the same size than the vector of glyphs. - rendererParameters.positions.Resize( numberOfGlyphs ); - - // Whether the last character is a new paragraph character. - layoutParameters.isLastNewParagraph = TextAbstraction::IsNewParagraph( textToShape[numberOfCharacters - 1u] ); - - // The initial glyph and the number of glyphs to layout. - layoutParameters.startGlyphIndex = 0u; - layoutParameters.numberOfGlyphs = numberOfGlyphs; - layoutParameters.startLineIndex = 0u; - layoutParameters.estimatedNumberOfLines = 1u; - layoutParameters.interGlyphExtraAdvance = 0.f; - - // Update the visual model. - Size newLayoutSize; - bool isAutoScrollEnabled = false; - layoutEngine.LayoutText( layoutParameters, - rendererParameters.positions, - lines, - newLayoutSize, - textParameters.ellipsisEnabled, - isAutoScrollEnabled ); - - //////////////////////////////////////////////////////////////////////////////// - // Reorder BiDirectional lines. - //////////////////////////////////////////////////////////////////////////////// - - if( hasBidirectionalText ) - { - // Reorder the line. - bidirectionalLineInfo.Reserve( 1u ); - - ReorderLines( bidirectionalInfo, - 0u, - numberOfCharacters, - lines, - bidirectionalLineInfo ); - - // Set the bidirectional info per line into the layout parameters. - layoutParameters.lineBidirectionalInfoRunsBuffer = bidirectionalLineInfo.Begin(); - layoutParameters.numberOfBidirectionalInfoRuns = bidirectionalLineInfo.Count(); - - // Re-layout the text. Reorder those lines with right to left characters. - layoutEngine.ReLayoutRightToLeftLines( layoutParameters, - 0u, - numberOfCharacters, - rendererParameters.positions ); - } - - //////////////////////////////////////////////////////////////////////////////// - // Align the text. - //////////////////////////////////////////////////////////////////////////////// + textModel->mHorizontalAlignment = isCircularTextLayout ? horizontalCircularAlignment : horizontalAlignment; // Retrieve the line of text to know the direction and the width. @todo multi-line const LineRun& line = lines[0u]; - if( isCircularTextLayout ) + if(isCircularTextLayout) { // Set the circular alignment. rendererParameters.circularLayout = isClockwise ? TextRenderer::Parameters::CLOCKWISE : TextRenderer::Parameters::COUNTER_CLOCKWISE; @@ -642,11 +561,11 @@ Devel::PixelBuffer Render( const RendererParameters& textParameters, Vector( newLayoutSize.width ); - rendererParameters.circularHeight = static_cast( newLayoutSize.height ); + rendererParameters.circularWidth = static_cast(newLayoutSize.width); + rendererParameters.circularHeight = static_cast(newLayoutSize.height); // Calculate the center of the circular text according the horizontal and vertical alingments and the radius. - switch( horizontalAlignment ) + switch(horizontalAlignment) { case HorizontalAlignment::BEGIN: { @@ -655,17 +574,17 @@ Devel::PixelBuffer Render( const RendererParameters& textParameters, Vector( textParameters.textWidth / 2u ); + rendererParameters.centerX = static_cast(textParameters.textWidth / 2u); break; } case HorizontalAlignment::END: { - rendererParameters.centerX = static_cast( textParameters.textWidth ) - static_cast(textParameters.radius); + rendererParameters.centerX = static_cast(textParameters.textWidth) - static_cast(textParameters.radius); break; } } - switch( verticalAlignment ) + switch(verticalAlignment) { case VerticalAlignment::TOP: { @@ -674,12 +593,12 @@ Devel::PixelBuffer Render( const RendererParameters& textParameters, Vector( textParameters.textHeight / 2u ); + rendererParameters.centerY = static_cast(textParameters.textHeight / 2u); break; } case VerticalAlignment::BOTTOM: { - rendererParameters.centerY = static_cast( textParameters.textHeight ) - static_cast(textParameters.radius); + rendererParameters.centerY = static_cast(textParameters.textHeight) - static_cast(textParameters.radius); break; } } @@ -688,10 +607,10 @@ Devel::PixelBuffer Render( const RendererParameters& textParameters, Vector( rendererParameters.circularWidth ); - angleOffset = ( isNeg ? -0.5f : 0.5f ) * ( textLayoutArea.width - textWidth ) / static_cast( radius ); + const bool isNeg = textParameters.incrementAngle < 0.f; + const float textWidth = static_cast(rendererParameters.circularWidth); + angleOffset = (isNeg ? -0.5f : 0.5f) * (textLayoutArea.width - textWidth) / static_cast(rendererParameters.radius); break; } case CircularAlignment::END: { - const bool isNeg = textParameters.incrementAngle < 0.f; - const float textWidth = static_cast( rendererParameters.circularWidth ); - angleOffset = ( isNeg ? -1.f : 1.f ) * ( textLayoutArea.width - textWidth ) / static_cast( radius ); + const bool isNeg = textParameters.incrementAngle < 0.f; + const float textWidth = static_cast(rendererParameters.circularWidth); + angleOffset = (isNeg ? -1.f : 1.f) * (textLayoutArea.width - textWidth) / static_cast(rendererParameters.radius); break; } } // Update the beginning angle with the calculated offset. - rendererParameters.beginAngle = Radian( Degree( textParameters.beginAngle ) ) + angleOffset; + rendererParameters.beginAngle = Radian(Degree(textParameters.beginAngle)) + angleOffset; // Set the vertical position of the glyphs. - for( auto& position : rendererParameters.positions ) + for(auto& position : rendererParameters.positions) { position.y = 0.f; } @@ -750,7 +669,7 @@ Devel::PixelBuffer Render( const RendererParameters& textParameters, Vector( embeddedItem.position.x + halfWidth ); - double centerY = static_cast(embeddedItem.position.y - halfHeight); + double centerX = static_cast(embeddedItem.position.x + halfWidth); + double centerY = static_cast(embeddedItem.position.y - halfHeight); Dali::TextAbstraction::CircularTextParameters circularTextParameters; - circularTextParameters.radius = static_cast( radius ); - circularTextParameters.invRadius = 1.0 / circularTextParameters.radius; - circularTextParameters.beginAngle = static_cast( -rendererParameters.beginAngle + Dali::Math::PI_2 ); - circularTextParameters.centerX = 0.5f * static_cast( textParameters.textWidth ); - circularTextParameters.centerY = 0.5f * static_cast( textParameters.textHeight ); + circularTextParameters.radius = static_cast(rendererParameters.radius); + circularTextParameters.invRadius = 1.0 / circularTextParameters.radius; + circularTextParameters.beginAngle = static_cast(-rendererParameters.beginAngle + Dali::Math::PI_2); + circularTextParameters.centerX = 0.5f * static_cast(textParameters.textWidth); + circularTextParameters.centerY = 0.5f * static_cast(textParameters.textHeight); // Calculate the rotation angle. float radians = rendererParameters.beginAngle; - if( isClockwise ) + if(isClockwise) { - radians += static_cast( circularTextParameters.invRadius * centerX ); + radians += static_cast(circularTextParameters.invRadius * centerX); radians = -radians; } else { - radians -= static_cast( circularTextParameters.invRadius * centerX ); + radians -= static_cast(circularTextParameters.invRadius * centerX); radians = -radians + Dali::Math::PI; } - embeddedItem.angle = Degree( Radian( radians ) ); + embeddedItem.angle = Degree(Radian(radians)); - Dali::TextAbstraction::TransformToArc( circularTextParameters, centerX, centerY ); + Dali::TextAbstraction::TransformToArc(circularTextParameters, centerX, centerY); // Recalculate the size of the embedded item after the rotation to position it correctly. - float width = embeddedItem.size.width; + float width = embeddedItem.size.width; float height = embeddedItem.size.height; // Transform the input angle into the range [0..2PI] - radians = fmod( radians, TWO_PI ); - radians += ( radians < 0.f ) ? TWO_PI : 0.f; + radians = fmod(radians, TWO_PI); + radians += (radians < 0.f) ? TWO_PI : 0.f; // Does the same operations than rotate by shear. - if( ( radians > Math::PI_4 ) && ( radians <= RAD_135 ) ) + if((radians > Math::PI_4) && (radians <= RAD_135)) { - std::swap( width, height ); + std::swap(width, height); radians -= Math::PI_2; } - else if( ( radians > RAD_135 ) && ( radians <= RAD_225 ) ) + else if((radians > RAD_135) && (radians <= RAD_225)) { radians -= Math::PI; } - else if( ( radians > RAD_225 ) && ( radians <= RAD_315 ) ) + else if((radians > RAD_225) && (radians <= RAD_315)) { - std::swap( width, height ); + std::swap(width, height); radians -= RAD_270; } - if( fabs( radians ) > Dali::Math::MACHINE_EPSILON_10 ) + if(fabs(radians) > Dali::Math::MACHINE_EPSILON_10) { - const float angleSinus = fabs( sin( radians ) ); - const float angleCosinus = cos( radians ); + const float angleSinus = fabs(sin(radians)); + const float angleCosinus = cos(radians); // Calculate the rotated image dimensions. embeddedItem.rotatedSize.height = width * angleSinus + height * angleCosinus; - embeddedItem.rotatedSize.width = height * angleSinus + width * angleCosinus + 1.f; + embeddedItem.rotatedSize.width = height * angleSinus + width * angleCosinus + 1.f; } - embeddedItem.position.x = floor( static_cast( centerX ) - 0.5f * embeddedItem.rotatedSize.width ); - embeddedItem.position.y = floor( static_cast( centerY ) - 0.5f * embeddedItem.rotatedSize.height ); + embeddedItem.position.x = floor(static_cast(centerX) - 0.5f * embeddedItem.rotatedSize.width); + embeddedItem.position.y = floor(static_cast(centerY) - 0.5f * embeddedItem.rotatedSize.height); } else { embeddedItem.position.y -= embeddedItem.size.height; } } +} + +void Ellipsis(const RendererParameters& textParameters, TextAbstraction::TextRenderer::Parameters& rendererParameters, Vector& embeddedItemLayout, InternalDataModel& internalDataModel) +{ + Text::ModelPtr& textModel = internalDataModel.textModel; + FontClient& fontClient = internalDataModel.fontClient; + Vector& lines = textModel->mVisualModel->mLines; // The laid out lines. + Vector& isEmoji = internalDataModel.isEmoji; + const Size textLayoutArea = internalDataModel.textLayoutArea; //////////////////////////////////////////////////////////////////////////////// // Ellipsis the text. //////////////////////////////////////////////////////////////////////////////// - if( textParameters.ellipsisEnabled ) + if(textParameters.ellipsisEnabled) { const LineRun& line = lines[0u]; // TODO: multi-line - if( line.ellipsis ) + if(line.ellipsis) { Length finalNumberOfGlyphs = 0u; - if( ( line.ascender - line.descender ) > textLayoutArea.height ) + if((line.ascender - line.descender) > textLayoutArea.height) { // The height of the line is bigger than the height of the text area. // Show the ellipsis glyph even if it doesn't fit in the text area. @@ -905,13 +833,13 @@ Devel::PixelBuffer Render( const RendererParameters& textParameters, Vector 0u) + if(line.glyphRun.numberOfGlyphs > 0u) { GlyphIndex index = line.glyphRun.numberOfGlyphs - 1u; - GlyphInfo* glyphs = rendererParameters.glyphs.Begin(); - Vector2* glyphPositions = rendererParameters.positions.Begin(); + GlyphInfo* glyphs = rendererParameters.glyphs.Begin(); + Vector2* glyphPositions = rendererParameters.positions.Begin(); float penY = 0.f; // The ellipsis glyph has to fit in the place where the last glyph(s) is(are) removed. - while( !inserted ) + while(!inserted) { - const GlyphInfo& glyphToRemove = *( glyphs + index ); + const GlyphInfo& glyphToRemove = *(glyphs + index); - if( 0u != glyphToRemove.fontId ) + if(0u != glyphToRemove.fontId) { // i.e. The font id of the glyph shaped from the '\n' character is zero. // Need to reshape the glyph as the font may be different in size. - const GlyphInfo& ellipsisGlyph = fontClient.GetEllipsisGlyph( fontClient.GetPointSize( glyphToRemove.fontId ) ); + const GlyphInfo& ellipsisGlyph = fontClient.GetEllipsisGlyph(fontClient.GetPointSize(glyphToRemove.fontId)); - if( !firstPenSet ) + if(!firstPenSet) { - const Vector2& position = *( glyphPositions + index ); + const Vector2& position = *(glyphPositions + index); // Calculates the penY of the current line. It will be used to position the ellipsis glyph. penY = position.y; // Calculates the first penX which will be used if rtl text is elided. firstPenX = position.x - glyphToRemove.xBearing; - if( firstPenX < -ellipsisGlyph.xBearing ) + if(firstPenX < -ellipsisGlyph.xBearing) { // Avoids to exceed the bounding box when rtl text is elided. firstPenX = -ellipsisGlyph.xBearing; @@ -971,15 +898,15 @@ Devel::PixelBuffer Render( const RendererParameters& textParameters, Vector glyphInfo.xBearing ) ? glyphInfo.xBearing : 0.f; + GlyphInfo& glyphInfo = *(glyphs + index); + Vector2& position = *(glyphPositions + index); + position.x -= (0.f > glyphInfo.xBearing) ? glyphInfo.xBearing : 0.f; // Replace the glyph by the ellipsis glyph. glyphInfo = ellipsisGlyph; @@ -989,7 +916,7 @@ Devel::PixelBuffer Render( const RendererParameters& textParameters, Vector firstPenX ) + if(position.x > firstPenX) { position.x = firstPenX + removedGlypsWidth - ellipsisGlyphWidth; } @@ -1001,9 +928,9 @@ Devel::PixelBuffer Render( const RendererParameters& textParameters, Vector 0u ) + if(index > 0u) { --index; } @@ -1021,46 +948,223 @@ Devel::PixelBuffer Render( const RendererParameters& textParameters, Vector= finalNumberOfGlyphs; - } ), - embeddedItemLayout.End() ); + embeddedItemLayout.Erase(std::remove_if(embeddedItemLayout.Begin(), + embeddedItemLayout.End(), + [finalNumberOfGlyphs](const EmbeddedItemInfo& item) { + return item.glyphIndex >= finalNumberOfGlyphs; + }), + embeddedItemLayout.End()); + } + } + } +} + +Size LayoutText(const RendererParameters& textParameters, TextAbstraction::TextRenderer::Parameters& rendererParameters, Vector& embeddedItemLayout, InternalDataModel& internalDataModel) +{ + //////////////////////////////////////////////////////////////////////////////// + // Layout the text. + //////////////////////////////////////////////////////////////////////////////// + Text::ModelPtr& textModel = internalDataModel.textModel; + Text::Layout::Engine& layoutEngine = internalDataModel.layoutEngine; + FontClient& fontClient = internalDataModel.fontClient; + const Length numberOfGlyphs = internalDataModel.numberOfGlyphs; + const bool isTextMirrored = internalDataModel.isTextMirrored; + const Vector& mirroredUtf32Characters = internalDataModel.mirroredUtf32Characters; + const Length numberOfCharacters = internalDataModel.numberOfCharacters; + + Layout::Type layout = Layout::SINGLELINE; + + Property::Value layoutStr(textParameters.layout); + GetLayoutEnumeration(layoutStr, layout); + + // Whether the layout is multi-line. + const Text::Layout::Engine::Type horizontalLayout = (Layout::MULTILINE == layout) ? Text::Layout::Engine::MULTI_LINE_BOX : Text::Layout::Engine::SINGLE_LINE_BOX; + layoutEngine.SetLayout(horizontalLayout); // TODO: multi-line. + + // Set minimun line size + layoutEngine.SetDefaultLineSize(textParameters.minLineSize); + + // Whether the layout is circular. + const bool isCircularTextLayout = (Layout::CIRCULAR == layout); + const bool isClockwise = isCircularTextLayout && (0.f < textParameters.incrementAngle); + + // Calculates the max ascender or the max descender. + // Is used to calculate the radius of the base line of the text. + float maxAscenderDescender = 0.f; + if(isCircularTextLayout) + { + FontId currentFontId = 0u; + for(const auto& glyph : rendererParameters.glyphs) + { + if(currentFontId != glyph.fontId) + { + currentFontId = glyph.fontId; + FontMetrics metrics; + fontClient.GetFontMetrics(currentFontId, metrics); + maxAscenderDescender = std::max(maxAscenderDescender, isClockwise ? metrics.ascender : metrics.descender); } } } + const unsigned int radius = textParameters.radius - static_cast(maxAscenderDescender); + + // Set the layout parameters. + Size textLayoutArea = Size(static_cast(textParameters.textWidth), + static_cast(textParameters.textHeight)); + + // padding + Extents padding = textParameters.padding; + internalDataModel.textLayoutArea = Size(textLayoutArea.x - ( padding.start + padding.end ), textLayoutArea.y - ( padding.top + padding.bottom ) ); + + + if(isCircularTextLayout) + { + // In a circular layout, the length of the text area depends on the radius. + rendererParameters.radius = radius; + internalDataModel.textLayoutArea.width = fabs(Radian(Degree(textParameters.incrementAngle)) * static_cast(rendererParameters.radius)); + } + // Resize the vector of positions to have the same size than the vector of glyphs. + rendererParameters.positions.Resize(numberOfGlyphs); + + textModel->mLineWrapMode = LineWrap::WORD; + textModel->mIgnoreSpacesAfterText = false; + textModel->mMatchSystemLanguageDirection = false; + Text::Layout::Parameters layoutParameters(internalDataModel.textLayoutArea, + textModel); + + // Whether the last character is a new paragraph character. + const Vector& textToShape = isTextMirrored ? mirroredUtf32Characters : textModel->mLogicalModel->mText; + layoutParameters.isLastNewParagraph = TextAbstraction::IsNewParagraph(textToShape[numberOfCharacters - 1u]); + + // The initial glyph and the number of glyphs to layout. + layoutParameters.startGlyphIndex = 0u; + layoutParameters.numberOfGlyphs = numberOfGlyphs; + layoutParameters.startLineIndex = 0u; + layoutParameters.estimatedNumberOfLines = 1u; + layoutParameters.interGlyphExtraAdvance = 0.f; + + // Update the visual model. + Size newLayoutSize; + bool isAutoScrollEnabled = false; + layoutEngine.LayoutText(layoutParameters, + newLayoutSize, + textParameters.ellipsisEnabled, + isAutoScrollEnabled); + return newLayoutSize; +} + +Devel::PixelBuffer RenderText(const RendererParameters& textParameters, TextAbstraction::TextRenderer::Parameters& rendererParameters) +{ //////////////////////////////////////////////////////////////////////////////// // Render the text. //////////////////////////////////////////////////////////////////////////////// - rendererParameters.width = textParameters.textWidth; + rendererParameters.width = textParameters.textWidth; rendererParameters.height = textParameters.textHeight; TextAbstraction::TextRenderer renderer = TextAbstraction::TextRenderer::Get(); - return renderer.Render( rendererParameters ); + return renderer.Render(rendererParameters); +} + +Devel::PixelBuffer Render(const RendererParameters& textParameters, Vector& embeddedItemLayout) +{ + if(textParameters.text.empty()) + { + Dali::Devel::PixelBuffer pixelBuffer = Dali::Devel::PixelBuffer::New(textParameters.textWidth, + textParameters.textHeight, + Dali::Pixel::RGBA8888); + + const unsigned int bufferSize = textParameters.textWidth * textParameters.textHeight * Dali::Pixel::GetBytesPerPixel(Dali::Pixel::RGBA8888); + unsigned char* buffer = pixelBuffer.GetBuffer(); + memset(buffer, 0, bufferSize); + + return pixelBuffer; + } + + FontClient fontClient = FontClient::Get(); + MetricsPtr metrics; + // Use this to access FontClient i.e. to get down-scaled Emoji metrics. + metrics = Metrics::New(fontClient); + + Text::ModelPtr textModel = Text::Model::New(); + InternalDataModel internalData(fontClient, metrics, textModel); + + TextAbstraction::TextRenderer::Parameters rendererParameters(internalData.textModel->mVisualModel->mGlyphs, + internalData.textModel->mVisualModel->mGlyphPositions, + internalData.textModel->mVisualModel->mColors, + internalData.textModel->mVisualModel->mColorIndices, + internalData.blendingMode, + internalData.isEmoji); + + rendererParameters.width = textParameters.textWidth; + rendererParameters.height = textParameters.textHeight; + rendererParameters.pixelFormat = TextAbstraction::TextRenderer::Parameters::RGBA8888; // @note: At the moment all textures are generated RGBA8888 + + //////////////////////////////////////////////////////////////////////////////// + // Process the markup string if the mark-up processor is enabled. + //////////////////////////////////////////////////////////////////////////////// + ShapeTextPreprocess(textParameters, rendererParameters, internalData); + + //////////////////////////////////////////////////////////////////////////////// + // Retrieve the glyphs. Text shaping + //////////////////////////////////////////////////////////////////////////////// + ShapeText(rendererParameters, embeddedItemLayout, internalData); + + //////////////////////////////////////////////////////////////////////////////// + // Retrieve the glyph's metrics. + //////////////////////////////////////////////////////////////////////////////// + + metrics->GetGlyphMetrics(rendererParameters.glyphs.Begin(), internalData.numberOfGlyphs); + + //////////////////////////////////////////////////////////////////////////////// + // Set the color runs in glyphs. + //////////////////////////////////////////////////////////////////////////////// + SetColorSegmentation(textParameters, internalData); + + //////////////////////////////////////////////////////////////////////////////// + // Set the isEmoji Vector + //////////////////////////////////////////////////////////////////////////////// + SetEmojiVector(internalData); + + //////////////////////////////////////////////////////////////////////////////// + // Layout the text + //////////////////////////////////////////////////////////////////////////////// + Size newLayoutSize = LayoutText(textParameters, rendererParameters, embeddedItemLayout, internalData); + + //////////////////////////////////////////////////////////////////////////////// + // Align the text. + //////////////////////////////////////////////////////////////////////////////// + Align(textParameters, rendererParameters, embeddedItemLayout, internalData, newLayoutSize); + + //////////////////////////////////////////////////////////////////////////////// + // Ellipsis the text. + //////////////////////////////////////////////////////////////////////////////// + Ellipsis(textParameters, rendererParameters, embeddedItemLayout, internalData); + + //////////////////////////////////////////////////////////////////////////////// + // Render the text. + //////////////////////////////////////////////////////////////////////////////// + return RenderText(textParameters, rendererParameters); } -Devel::PixelBuffer CreateShadow( const ShadowParameters& shadowParameters ) +Devel::PixelBuffer CreateShadow(const ShadowParameters& shadowParameters) { // The size of the pixel data. - const int width = static_cast(shadowParameters.input.GetWidth()); + const int width = static_cast(shadowParameters.input.GetWidth()); const int height = static_cast(shadowParameters.input.GetHeight()); // The shadow's offset. - const int xOffset = static_cast( shadowParameters.offset.x ); - const int yOffset = static_cast( shadowParameters.offset.y ); + const int xOffset = static_cast(shadowParameters.offset.x); + const int yOffset = static_cast(shadowParameters.offset.y); // The size in bytes of the pixel of the input's buffer. - const Pixel::Format inputFormat = shadowParameters.input.GetPixelFormat(); - const unsigned int inputPixelSize = Pixel::GetBytesPerPixel( inputFormat ); - const bool isA8 = Pixel::A8 == inputFormat; + const Pixel::Format inputFormat = shadowParameters.input.GetPixelFormat(); + const unsigned int inputPixelSize = Pixel::GetBytesPerPixel(inputFormat); + const bool isA8 = Pixel::A8 == inputFormat; // Creates the output pixel buffer. Devel::PixelBuffer outputPixelBuffer = Devel::PixelBuffer::New(width, height, Pixel::RGBA8888); @@ -1073,35 +1177,35 @@ Devel::PixelBuffer CreateShadow( const ShadowParameters& shadowParameters ) const unsigned char* const inputPixelBuffer = shadowParameters.input.GetBuffer(); float textColor[4u]; - if (isA8) + if(isA8) { memcpy(textColor, shadowParameters.textColor.AsFloat(), 4u * sizeof(float)); } const float* const shadowColor = shadowParameters.color.AsFloat(); // Traverse the input pixel buffer and write the text on the foreground and the shadow on the background. - for (int rowIndex = 0; rowIndex < height; ++rowIndex) + for(int rowIndex = 0; rowIndex < height; ++rowIndex) { // Calculates the rowIndex to the input pixel buffer for the shadow and whether it's within the boundaries. - const int yOffsetIndex = rowIndex - yOffset; + const int yOffsetIndex = rowIndex - yOffset; const bool isValidRowIndex = ((yOffsetIndex >= 0) && (yOffsetIndex < height)); - const int rows = rowIndex * width; + const int rows = rowIndex * width; const int offsetRows = yOffsetIndex * width; - for (int columnIndex = 0; columnIndex < width; ++columnIndex) + for(int columnIndex = 0; columnIndex < width; ++columnIndex) { // Index to the input buffer to retrieve the alpha value of the foreground text. const unsigned int index = inputPixelSize * static_cast(rows + columnIndex); // Build the index to the input buffer to retrieve the alpha value of the background shadow. - unsigned int shadowIndex = 0u; - bool isValidShadowIndex = false; - if (isValidRowIndex) + unsigned int shadowIndex = 0u; + bool isValidShadowIndex = false; + if(isValidRowIndex) { const int xOffsetIndex = columnIndex - xOffset; - isValidShadowIndex = ((xOffsetIndex >= 0) && (xOffsetIndex < width)); + isValidShadowIndex = ((xOffsetIndex >= 0) && (xOffsetIndex < width)); - if (isValidShadowIndex) + if(isValidShadowIndex) { shadowIndex = inputPixelSize * static_cast(offsetRows + xOffsetIndex); } @@ -1110,8 +1214,8 @@ Devel::PixelBuffer CreateShadow( const ShadowParameters& shadowParameters ) // If the input buffer is an alpha mask, retrieve the values for the foreground text and the background shadow. // If not retrieve the color. float inputShadowOffsetAlphaValue = 1.f; - float inputAlphaValue = 1.f; - if (isA8) + float inputAlphaValue = 1.f; + if(isA8) { // Retrieve the alpha value for the shadow. inputShadowOffsetAlphaValue = isValidShadowIndex ? (static_cast(*(inputPixelBuffer + shadowIndex)) / 255.f) : 0.f; @@ -1122,59 +1226,59 @@ Devel::PixelBuffer CreateShadow( const ShadowParameters& shadowParameters ) else { // The input buffer is not an alpha mask. Retrieve the color. - textColor[0u] = TO_FLOAT * static_cast( *(inputPixelBuffer + index + 0u) ); - textColor[1u] = TO_FLOAT * static_cast( *(inputPixelBuffer + index + 1u) ); - textColor[2u] = TO_FLOAT * static_cast( *(inputPixelBuffer + index + 2u) ); - textColor[3u] = TO_FLOAT * static_cast( *(inputPixelBuffer + index + 3u) ); - inputAlphaValue = textColor[3u]; - inputShadowOffsetAlphaValue = isValidShadowIndex ? TO_FLOAT * static_cast( *(inputPixelBuffer + shadowIndex + 3u) ) : 0.f; + textColor[0u] = TO_FLOAT * static_cast(*(inputPixelBuffer + index + 0u)); + textColor[1u] = TO_FLOAT * static_cast(*(inputPixelBuffer + index + 1u)); + textColor[2u] = TO_FLOAT * static_cast(*(inputPixelBuffer + index + 2u)); + textColor[3u] = TO_FLOAT * static_cast(*(inputPixelBuffer + index + 3u)); + inputAlphaValue = textColor[3u]; + inputShadowOffsetAlphaValue = isValidShadowIndex ? TO_FLOAT * static_cast(*(inputPixelBuffer + shadowIndex + 3u)) : 0.f; } // Build the output color. float outputColor[4u]; - if( shadowParameters.blendShadow ) + if(shadowParameters.blendShadow) { // Blend the shadow's color with the text's color on top - const float textAlpha = textColor[3u] * inputAlphaValue; + const float textAlpha = textColor[3u] * inputAlphaValue; const float shadowAlpha = shadowColor[3u] * inputShadowOffsetAlphaValue; // Blends the alpha. - outputColor[3u] = 1.f - ((1.f - textAlpha) * (1.f - shadowAlpha)); + outputColor[3u] = 1.f - ((1.f - textAlpha) * (1.f - shadowAlpha)); const bool isOutputAlphaZero = outputColor[3u] < Dali::Math::MACHINE_EPSILON_1000; - if( isOutputAlphaZero ) + if(isOutputAlphaZero) { - std::fill( outputColor, outputColor + 4u, 0.f ); + std::fill(outputColor, outputColor + 4u, 0.f); } else { // Blends the RGB components. float shadowComponent = 0.f; - float textComponent = 0.f; + float textComponent = 0.f; shadowComponent = shadowColor[0u] * inputShadowOffsetAlphaValue; - textComponent = textColor[0u] * inputAlphaValue; - outputColor[0u] = (textComponent * textAlpha / outputColor[3u]) + (shadowComponent * shadowAlpha * (1.f - textAlpha) / outputColor[3u]); + textComponent = textColor[0u] * inputAlphaValue; + outputColor[0u] = (textComponent * textAlpha / outputColor[3u]) + (shadowComponent * shadowAlpha * (1.f - textAlpha) / outputColor[3u]); shadowComponent = shadowColor[1u] * inputShadowOffsetAlphaValue; - textComponent = textColor[1u] * inputAlphaValue; - outputColor[1u] = (textComponent * textAlpha / outputColor[3u]) + (shadowComponent * shadowAlpha * (1.f - textAlpha) / outputColor[3u]); + textComponent = textColor[1u] * inputAlphaValue; + outputColor[1u] = (textComponent * textAlpha / outputColor[3u]) + (shadowComponent * shadowAlpha * (1.f - textAlpha) / outputColor[3u]); shadowComponent = shadowColor[2u] * inputShadowOffsetAlphaValue; - textComponent = textColor[2u] * inputAlphaValue; - outputColor[2u] = (textComponent * textAlpha / outputColor[3u]) + (shadowComponent * shadowAlpha * (1.f - textAlpha) / outputColor[3u]); + textComponent = textColor[2u] * inputAlphaValue; + outputColor[2u] = (textComponent * textAlpha / outputColor[3u]) + (shadowComponent * shadowAlpha * (1.f - textAlpha) / outputColor[3u]); } } else { // No blending!!! - std::fill( outputColor, outputColor + 4u, 0.f ); + std::fill(outputColor, outputColor + 4u, 0.f); - const float textAlpha = textColor[3u]; + const float textAlpha = textColor[3u]; const float shadowAlpha = shadowColor[3u] * inputShadowOffsetAlphaValue; // Write shadow first. - if( shadowAlpha > Dali::Math::MACHINE_EPSILON_1000 ) + if(shadowAlpha > Dali::Math::MACHINE_EPSILON_1000) { outputColor[0u] = shadowColor[0u] * inputShadowOffsetAlphaValue; outputColor[1u] = shadowColor[1u] * inputShadowOffsetAlphaValue; @@ -1183,7 +1287,7 @@ Devel::PixelBuffer CreateShadow( const ShadowParameters& shadowParameters ) } // Write character on top. - if( textAlpha > Dali::Math::MACHINE_EPSILON_1000 ) + if(textAlpha > Dali::Math::MACHINE_EPSILON_1000) { outputColor[0u] = textColor[0u]; outputColor[1u] = textColor[1u]; @@ -1193,11 +1297,11 @@ Devel::PixelBuffer CreateShadow( const ShadowParameters& shadowParameters ) } // Write the color into the output pixel buffer. - const unsigned int outputIndex = 4u * (rows + columnIndex); - *(outputPixelBufferPtr + outputIndex + 0u) = static_cast( TO_UCHAR * outputColor[0u] ); - *(outputPixelBufferPtr + outputIndex + 1u) = static_cast( TO_UCHAR * outputColor[1u] ); - *(outputPixelBufferPtr + outputIndex + 2u) = static_cast( TO_UCHAR * outputColor[2u] ); - *(outputPixelBufferPtr + outputIndex + 3u) = static_cast( TO_UCHAR * outputColor[3u] ); + const unsigned int outputIndex = 4u * (rows + columnIndex); + *(outputPixelBufferPtr + outputIndex + 0u) = static_cast(TO_UCHAR * outputColor[0u]); + *(outputPixelBufferPtr + outputIndex + 1u) = static_cast(TO_UCHAR * outputColor[1u]); + *(outputPixelBufferPtr + outputIndex + 2u) = static_cast(TO_UCHAR * outputColor[2u]); + *(outputPixelBufferPtr + outputIndex + 3u) = static_cast(TO_UCHAR * outputColor[3u]); } } @@ -1207,50 +1311,50 @@ Devel::PixelBuffer CreateShadow( const ShadowParameters& shadowParameters ) Devel::PixelBuffer ConvertToRgba8888(Devel::PixelBuffer pixelBuffer, const Vector4& color, bool multiplyByAlpha) { - if (Dali::Pixel::A8 != pixelBuffer.GetPixelFormat()) + if(Dali::Pixel::A8 != pixelBuffer.GetPixelFormat()) { // Does nothing. return pixelBuffer; } - const unsigned int width = pixelBuffer.GetWidth(); - const unsigned int height = pixelBuffer.GetHeight(); - Devel::PixelBuffer newPixelBuffer = Devel::PixelBuffer::New( width, height, Dali::Pixel::RGBA8888 ); + const unsigned int width = pixelBuffer.GetWidth(); + const unsigned int height = pixelBuffer.GetHeight(); + Devel::PixelBuffer newPixelBuffer = Devel::PixelBuffer::New(width, height, Dali::Pixel::RGBA8888); - unsigned char* dstBuffer = newPixelBuffer.GetBuffer(); + unsigned char* dstBuffer = newPixelBuffer.GetBuffer(); const unsigned char* const srcBuffer = pixelBuffer.GetBuffer(); - const unsigned char r = static_cast( TO_UCHAR * color.r ); - const unsigned char g = static_cast( TO_UCHAR * color.g ); - const unsigned char b = static_cast( TO_UCHAR * color.b ); + const unsigned char r = static_cast(TO_UCHAR * color.r); + const unsigned char g = static_cast(TO_UCHAR * color.g); + const unsigned char b = static_cast(TO_UCHAR * color.b); unsigned char dstColor[4]; - for( unsigned int j = 0u; j < height; ++j ) + for(unsigned int j = 0u; j < height; ++j) { const unsigned int lineIndex = j * width; - for( unsigned int i=0u; i < width; ++i ) + for(unsigned int i = 0u; i < width; ++i) { const unsigned int srcIndex = lineIndex + i; - const float srcAlpha = static_cast( *( srcBuffer + srcIndex ) ); + const float srcAlpha = static_cast(*(srcBuffer + srcIndex)); - if( multiplyByAlpha ) + if(multiplyByAlpha) { - dstColor[0u] = static_cast( srcAlpha * color.r ); - dstColor[1u] = static_cast( srcAlpha * color.g ); - dstColor[2u] = static_cast( srcAlpha * color.b ); - dstColor[3u] = static_cast( srcAlpha * color.a ); + dstColor[0u] = static_cast(srcAlpha * color.r); + dstColor[1u] = static_cast(srcAlpha * color.g); + dstColor[2u] = static_cast(srcAlpha * color.b); + dstColor[3u] = static_cast(srcAlpha * color.a); } else { dstColor[0u] = r; dstColor[1u] = g; dstColor[2u] = b; - dstColor[3u] = static_cast( srcAlpha ); + dstColor[3u] = static_cast(srcAlpha); } const unsigned int dstIndex = srcIndex * 4u; - memcpy( dstBuffer + dstIndex, dstColor, 4u ); + memcpy(dstBuffer + dstIndex, dstColor, 4u); } } @@ -1260,42 +1364,42 @@ Devel::PixelBuffer ConvertToRgba8888(Devel::PixelBuffer pixelBuffer, const Vecto void UpdateBuffer(Devel::PixelBuffer src, Devel::PixelBuffer dst, unsigned int x, unsigned int y, bool blend) { const Dali::Pixel::Format pixelFormat = dst.GetPixelFormat(); - if( src.GetPixelFormat() != pixelFormat ) + if(src.GetPixelFormat() != pixelFormat) { DALI_LOG_ERROR("PixelBuffer::SetBuffer. The pixel format of the new data must be the same of the current pixel buffer."); return; } - const unsigned int srcWidth = src.GetWidth(); + const unsigned int srcWidth = src.GetWidth(); const unsigned int srcHeight = src.GetHeight(); - const unsigned int dstWidth = dst.GetWidth(); + const unsigned int dstWidth = dst.GetWidth(); const unsigned int dstHeight = dst.GetHeight(); - if( ( x > dstWidth ) || - ( y > dstHeight ) || - ( x + srcWidth > dstWidth ) || - ( y + srcHeight > dstHeight ) ) + if((x > dstWidth) || + (y > dstHeight) || + (x + srcWidth > dstWidth) || + (y + srcHeight > dstHeight)) { DALI_LOG_ERROR("PixelBuffer::SetBuffer. The source pixel buffer is out of the boundaries of the destination pixel buffer."); return; } const unsigned int bytesPerPixel = Dali::Pixel::GetBytesPerPixel(pixelFormat); - if( bytesPerPixel == 0u ) + if(bytesPerPixel == 0u || bytesPerPixel == 12u || bytesPerPixel == 24u) { return; } const unsigned int alphaIndex = bytesPerPixel - 1u; const unsigned char* const srcBuffer = src.GetBuffer(); - unsigned char* dstBuffer = dst.GetBuffer(); + unsigned char* dstBuffer = dst.GetBuffer(); - if( !blend ) + if(!blend) { const unsigned int currentLineSize = dstWidth * bytesPerPixel; - const unsigned int newLineSize = srcWidth * bytesPerPixel; - unsigned char* currentBuffer = dstBuffer + (y * dstWidth + x) * bytesPerPixel; - for (unsigned int j = 0u; j < srcHeight; ++j) + const unsigned int newLineSize = srcWidth * bytesPerPixel; + unsigned char* currentBuffer = dstBuffer + (y * dstWidth + x) * bytesPerPixel; + for(unsigned int j = 0u; j < srcHeight; ++j) { memcpy(currentBuffer + j * currentLineSize, srcBuffer + j * newLineSize, newLineSize); } @@ -1313,41 +1417,41 @@ void UpdateBuffer(Devel::PixelBuffer src, Devel::PixelBuffer dst, unsigned int x // Jump till the 'x,y' position const unsigned int dstWidthBytes = dstWidth * bytesPerPixel; - dstBuffer += ( y * dstWidthBytes + x * bytesPerPixel ); + dstBuffer += (y * dstWidthBytes + x * bytesPerPixel); - for (unsigned int j = 0u; j < srcHeight; ++j) + for(unsigned int j = 0u; j < srcHeight; ++j) { const unsigned int srcLineIndex = j * srcWidth; - for (unsigned int i = 0u; i < srcWidth; ++i) + for(unsigned int i = 0u; i < srcWidth; ++i) { - const float srcAlpha = TO_FLOAT * static_cast( *( srcBuffer + bytesPerPixel * ( srcLineIndex + i ) + alphaIndex ) ); - const float dstAlpha = TO_FLOAT * static_cast( *(dstBuffer + i*bytesPerPixel + alphaIndex) ); + const float srcAlpha = TO_FLOAT * static_cast(*(srcBuffer + bytesPerPixel * (srcLineIndex + i) + alphaIndex)); + const float dstAlpha = TO_FLOAT * static_cast(*(dstBuffer + i * bytesPerPixel + alphaIndex)); // Blends the alpha channel. const float oneMinusSrcAlpha = 1.f - srcAlpha; - outputColor[alphaIndex] = 1.f - (oneMinusSrcAlpha * (1.f - dstAlpha)); + outputColor[alphaIndex] = 1.f - (oneMinusSrcAlpha * (1.f - dstAlpha)); // Blends the RGB channels. const bool isOutputAlphaZero = outputColor[alphaIndex] < Dali::Math::MACHINE_EPSILON_1000; - if( isOutputAlphaZero ) + if(isOutputAlphaZero) { - std::fill( outputColor, outputColor + bytesPerPixel, 0.f ); + std::fill(outputColor, outputColor + bytesPerPixel, 0.f); } else { - const float srcAlphaOverOutputAlpha = srcAlpha / outputColor[alphaIndex]; // fgAlpha / alpha + const float srcAlphaOverOutputAlpha = srcAlpha / outputColor[alphaIndex]; // fgAlpha / alpha const float dstAlphaOneMinusSrcAlphaOverOutputAlpha = dstAlpha * oneMinusSrcAlpha / outputColor[alphaIndex]; // bgAlpha * ( 1 - fgAlpha ) / alpha - for (unsigned int index = 0u; index < alphaIndex; ++index) + for(unsigned int index = 0u; index < alphaIndex; ++index) { - const float dstComponent = TO_FLOAT * static_cast( *( dstBuffer + i * bytesPerPixel + index ) ) * dstAlpha; - const float srcComponent = TO_FLOAT * static_cast(*(srcBuffer + bytesPerPixel * (srcLineIndex + i) + index) ) * srcAlpha; - outputColor[index] = ( srcComponent * srcAlphaOverOutputAlpha ) + ( dstComponent * dstAlphaOneMinusSrcAlphaOverOutputAlpha ); + const float dstComponent = TO_FLOAT * static_cast(*(dstBuffer + i * bytesPerPixel + index)) * dstAlpha; + const float srcComponent = TO_FLOAT * static_cast(*(srcBuffer + bytesPerPixel * (srcLineIndex + i) + index)) * srcAlpha; + outputColor[index] = (srcComponent * srcAlphaOverOutputAlpha) + (dstComponent * dstAlphaOneMinusSrcAlphaOverOutputAlpha); } } - for (unsigned int index = 0u; index < bytesPerPixel; ++index) + for(unsigned int index = 0u; index < bytesPerPixel; ++index) { - *(dstBuffer + i * bytesPerPixel + index) = static_cast( TO_UCHAR * outputColor[index] ); + *(dstBuffer + i * bytesPerPixel + index) = static_cast(TO_UCHAR * outputColor[index]); } } @@ -1356,6 +1460,90 @@ void UpdateBuffer(Devel::PixelBuffer src, Devel::PixelBuffer dst, unsigned int x } } +Dali::Property::Array RenderForLastIndex(RendererParameters& textParameters) +{ + Property::Array offsetValues; + if(textParameters.text.empty()) + { + return offsetValues; + } + FontClient fontClient = FontClient::Get(); + MetricsPtr metrics; + metrics = Metrics::New(fontClient); + + Text::ModelPtr textModel = Text::Model::New(); + InternalDataModel internalData(fontClient, metrics, textModel); + + TextAbstraction::TextRenderer::Parameters rendererParameters(textModel->mVisualModel->mGlyphs, + textModel->mVisualModel->mGlyphPositions, + textModel->mVisualModel->mColors, + textModel->mVisualModel->mColorIndices, + internalData.blendingMode, + internalData.isEmoji); + + rendererParameters.width = textParameters.textWidth; + rendererParameters.height = textParameters.textHeight; + + //////////////////////////////////////////////////////////////////////////////// + // Process the markup string if the mark-up processor is enabled. + //////////////////////////////////////////////////////////////////////////////// + ShapeTextPreprocess(textParameters, rendererParameters, internalData); + + //////////////////////////////////////////////////////////////////////////////// + // Retrieve the glyphs. Text shaping + //////////////////////////////////////////////////////////////////////////////// + Dali::Vector embeddedItemLayout; + ShapeText(rendererParameters, embeddedItemLayout, internalData); + + //////////////////////////////////////////////////////////////////////////////// + // Retrieve the glyph's metrics. + //////////////////////////////////////////////////////////////////////////////// + metrics->GetGlyphMetrics(rendererParameters.glyphs.Begin(), internalData.numberOfGlyphs); + + //////////////////////////////////////////////////////////////////////////////// + // Layout the text + //////////////////////////////////////////////////////////////////////////////// + int boundingBox = textParameters.textHeight - (textParameters.padding.top + textParameters.padding.bottom); + textParameters.textHeight = MAX_INT; // layout for the entire area. + LayoutText(textParameters, rendererParameters, embeddedItemLayout, internalData); + + //////////////////////////////////////////////////////////////////////////////// + // Calculation last character index + //////////////////////////////////////////////////////////////////////////////// + Vector& lines = internalData.textModel->mVisualModel->mLines; + unsigned int numberOfLines = lines.Count(); + int numberOfCharacters = 0; + float penY = 0.f; + float lineSize = internalData.layoutEngine.GetDefaultLineSize(); + float lineOffset = 0.f; + for(unsigned int index = 0u; index < numberOfLines; ++index) + { + const LineRun& line = *(lines.Begin() + index); + numberOfCharacters += line.characterRun.numberOfCharacters; + + lineOffset = lineSize > 0.f ? lineSize : (line.ascender + -line.descender); + penY += lineOffset; + if((penY + lineOffset) > boundingBox) + { + offsetValues.PushBack(numberOfCharacters); + penY = 0.f; + } + } + if(penY > 0.f) + { + // add remain character index + offsetValues.PushBack(numberOfCharacters); + } + + return offsetValues; +} + +Dali::Property::Array GetLastCharacterIndex(RendererParameters& textParameters) +{ + Dali::Property::Array offsetValues = Toolkit::DevelText::RenderForLastIndex(textParameters); + return offsetValues; +} + } // namespace DevelText } // namespace Toolkit