Copyless glyph bitmap creation
[platform/core/uifw/dali-toolkit.git] / dali-toolkit / internal / text / rendering / atlas / text-atlas-renderer.cpp
index 626ce33..5913134 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2021 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2022 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.
 #include <dali/public-api/animation/constraints.h>
 #include <dali/public-api/rendering/geometry.h>
 #include <dali/public-api/rendering/renderer.h>
+#include <map>
 
 // INTERNAL INCLUDES
 #include <dali-toolkit/devel-api/controls/control-depth-index-ranges.h>
 #include <dali-toolkit/internal/graphics/builtin-shader-extern-gen.h>
+#include <dali-toolkit/internal/text/glyph-metrics-helper.h>
 #include <dali-toolkit/internal/text/glyph-run.h>
 #include <dali-toolkit/internal/text/rendering/atlas/atlas-glyph-manager.h>
 #include <dali-toolkit/internal/text/rendering/atlas/atlas-mesh-factory.h>
+#include <dali-toolkit/internal/text/rendering/styles/strikethrough-helper-functions.h>
+#include <dali-toolkit/internal/text/rendering/styles/underline-helper-functions.h>
 #include <dali-toolkit/internal/text/text-view.h>
 
 using namespace Dali;
@@ -46,6 +50,7 @@ Debug::Filter* gLogFilter = Debug::Filter::New(Debug::NoLogging, true, "LOG_TEXT
 const float    ZERO(0.0f);
 const float    HALF(0.5f);
 const float    ONE(1.0f);
+const float    ONE_AND_A_HALF(1.5f);
 const uint32_t DOUBLE_PIXEL_PADDING = 4u; //Padding will be added twice to Atlas
 const uint16_t NO_OUTLINE           = 0u;
 } // namespace
@@ -83,7 +88,8 @@ struct AtlasRenderer::Impl
       mLineThickness(0.0f),
       mMeshRecordIndex(0u),
       mUnderlineChunkId(0u),
-      mStrikethroughPosition(0.0f)
+      mStrikethroughPosition(0.0f),
+      mStrikethroughChunkId(0u)
     {
     }
 
@@ -95,6 +101,7 @@ struct AtlasRenderer::Impl
     uint32_t mMeshRecordIndex;
     uint32_t mUnderlineChunkId;
     float    mStrikethroughPosition;
+    uint32_t mStrikethroughChunkId;
   };
 
   struct MaxBlockSize
@@ -154,25 +161,6 @@ struct AtlasRenderer::Impl
     mQuadVertexFormat["aColor"]    = Property::VECTOR4;
   }
 
-  bool IsGlyphUnderlined(GlyphIndex              index,
-                         const Vector<GlyphRun>& underlineRuns)
-  {
-    for(Vector<GlyphRun>::ConstIterator it    = underlineRuns.Begin(),
-                                        endIt = underlineRuns.End();
-        it != endIt;
-        ++it)
-    {
-      const GlyphRun& run = *it;
-
-      if((run.glyphIndex <= index) && (index < run.glyphIndex + run.numberOfGlyphs))
-      {
-        return true;
-      }
-    }
-
-    return false;
-  }
-
   void CacheGlyph(const GlyphInfo& glyph, FontId lastFontId, const AtlasGlyphManager::GlyphStyle& style, AtlasManager::AtlasSlot& slot)
   {
     const Size& defaultTextAtlasSize = mFontClient.GetDefaultTextAtlasSize(); //Retrieve default size of text-atlas-block from font-client.
@@ -227,13 +215,35 @@ struct AtlasRenderer::Impl
                                  glyphBufferData,
                                  style.outline);
 
+        uint32_t glyphBufferSize = glyphBufferData.width * glyphBufferData.height * Pixel::GetBytesPerPixel(glyphBufferData.format);
+        // If glyph buffer data don't have ownership, Or if we need to decompress, create new memory and replace ownership.
+        if(!glyphBufferData.isBufferOwned || glyphBufferData.compressType != TextAbstraction::FontClient::GlyphBufferData::CompressType::NO_COMPRESS)
+        {
+          uint8_t* newBuffer = (uint8_t*)malloc(glyphBufferSize);
+          if(DALI_LIKELY(newBuffer != nullptr))
+          {
+            TextAbstraction::FontClient::GlyphBufferData::Decompress(glyphBufferData, newBuffer);
+            if(glyphBufferData.isBufferOwned)
+            {
+              // Release previous buffer
+              free(glyphBufferData.buffer);
+            }
+            glyphBufferData.isBufferOwned = true;
+            glyphBufferData.buffer        = newBuffer;
+            glyphBufferData.compressType  = TextAbstraction::FontClient::GlyphBufferData::CompressType::NO_COMPRESS;
+          }
+        }
+
         // Create the pixel data.
         bitmap = PixelData::New(glyphBufferData.buffer,
-                                glyphBufferData.width * glyphBufferData.height * GetBytesPerPixel(glyphBufferData.format),
+                                glyphBufferSize,
                                 glyphBufferData.width,
                                 glyphBufferData.height,
                                 glyphBufferData.format,
-                                PixelData::DELETE_ARRAY);
+                                PixelData::FREE);
+
+        // Change buffer ownership.
+        glyphBufferData.isBufferOwned = false;
 
         if(bitmap)
         {
@@ -292,7 +302,8 @@ struct AtlasRenderer::Impl
                     Vector<TextCacheEntry>&  newTextCache,
                     Vector<Extent>&          extents,
                     uint32_t                 underlineChunkId,
-                    bool                     isGlyphCached)
+                    bool                     isGlyphCached,
+                    uint32_t                 strikethroughChunkId)
   {
     // Generate mesh data for this quad, plugging in our supplied position
     AtlasManager::Mesh2D newMesh;
@@ -323,6 +334,10 @@ struct AtlasRenderer::Impl
       vertex.mColor = color;
     }
 
+    // Since Free Type font doesn't contain the strikethrough-position property,
+    // strikethrough position will be calculated by moving the underline position upwards by half the value of the line height.
+    float strikethroughStartingYPosition = (position.y + glyph.yBearing + currentUnderlinePosition) - ((glyph.height) * HALF);
+
     // Find an existing mesh data object to attach to ( or create a new one, if we can't find one using the same atlas)
     StitchTextMesh(meshContainer,
                    newMesh,
@@ -333,7 +348,8 @@ struct AtlasRenderer::Impl
                    currentlineThickness,
                    slot,
                    underlineChunkId,
-                   position.y + (glyph.height * HALF));
+                   strikethroughStartingYPosition,
+                   strikethroughChunkId);
   }
 
   void CreateActors(const std::vector<MeshRecord>& meshContainer,
@@ -431,17 +447,14 @@ struct AtlasRenderer::Impl
     const Vector2&   shadowOffset(view.GetShadowOffset());
     const Vector4&   shadowColor(view.GetShadowColor());
     const bool       underlineEnabled = view.IsUnderlineEnabled();
-    const Vector4&   underlineColor(view.GetUnderlineColor());
-    const float      underlineHeight = view.GetUnderlineHeight();
-    const uint16_t   outlineWidth    = view.GetOutlineWidth();
+    const uint16_t   outlineWidth     = view.GetOutlineWidth();
     const Vector4&   outlineColor(view.GetOutlineColor());
     const bool       isOutline            = 0u != outlineWidth;
     const GlyphInfo* hyphens              = view.GetHyphens();
     const Length*    hyphenIndices        = view.GetHyphenIndices();
     const Length     hyphensCount         = view.GetHyphensCount();
     const bool       strikethroughEnabled = view.IsStrikethroughEnabled();
-    const Vector4&   strikethroughColor(view.GetStrikethroughColor());
-    const float      strikethroughHeight = view.GetStrikethroughHeight();
+    const float      characterSpacing(view.GetCharacterSpacing());
 
     // Elided text info. Indices according to elided text.
     const auto startIndexOfGlyphs              = view.GetStartIndexOfElidedGlyphs();
@@ -450,23 +463,49 @@ struct AtlasRenderer::Impl
 
     const bool useDefaultColor = (NULL == colorsBuffer);
 
+    // Get a handle of the font client. Used to retrieve the bitmaps of the glyphs.
+    TextAbstraction::FontClient fontClient = TextAbstraction::FontClient::Get();
+
     // Get the underline runs.
-    const Length     numberOfUnderlineRuns = view.GetNumberOfUnderlineRuns();
-    Vector<GlyphRun> underlineRuns;
+    const Length               numberOfUnderlineRuns = view.GetNumberOfUnderlineRuns();
+    Vector<UnderlinedGlyphRun> underlineRuns;
     underlineRuns.Resize(numberOfUnderlineRuns);
     view.GetUnderlineRuns(underlineRuns.Begin(),
                           0u,
                           numberOfUnderlineRuns);
 
-    bool thereAreUnderlinedGlyphs = false;
-    bool strikethroughGlyphsExist = false;
-
-    float  currentUnderlinePosition      = ZERO;
-    float  currentUnderlineThickness     = underlineHeight;
-    float  currentStrikethroughThickness = strikethroughHeight;
-    FontId lastFontId                    = 0;
-    FontId lastUnderlinedFontId          = 0;
-    Style  style                         = STYLE_NORMAL;
+    // Aggregate underline-style-properties from view
+    const UnderlineStyleProperties viewUnderlineProperties{view.GetUnderlineType(),
+                                                           view.GetUnderlineColor(),
+                                                           view.GetUnderlineHeight(),
+                                                           view.GetDashedUnderlineGap(),
+                                                           view.GetDashedUnderlineWidth(),
+                                                           true,
+                                                           true,
+                                                           true,
+                                                           true,
+                                                           true};
+
+    float maxUnderlineHeight = viewUnderlineProperties.height;
+
+    // Get the strikethrough runs.
+    const Length                  numberOfStrikethroughRuns = view.GetNumberOfStrikethroughRuns();
+    Vector<StrikethroughGlyphRun> strikethroughRuns;
+    strikethroughRuns.Resize(numberOfStrikethroughRuns);
+    view.GetStrikethroughRuns(strikethroughRuns.Begin(), 0u, numberOfStrikethroughRuns);
+
+    const StrikethroughStyleProperties viewStrikethroughProperties{view.GetStrikethroughColor(),
+                                                                   view.GetStrikethroughHeight(),
+                                                                   true,
+                                                                   true};
+
+    float maxStrikethroughHeight = viewStrikethroughProperties.height;
+
+    FontId lastFontId                  = 0;
+    Style  style                       = STYLE_NORMAL;
+    float  currentUnderlinePosition    = ZERO;
+    bool   thereAreUnderlinedGlyphs    = false;
+    bool   thereAreStrikethroughGlyphs = false;
 
     if(fabsf(shadowOffset.x) > Math::MACHINE_EPSILON_1 || fabsf(shadowOffset.y) > Math::MACHINE_EPSILON_1)
     {
@@ -483,8 +522,21 @@ struct AtlasRenderer::Impl
     uint32_t               hyphenIndex = 0;
 
     //For septated underlined chunks. (this is for Markup case)
-    uint32_t underlineChunkId = 0u;    // give id for each chunk.
-    bool     isPreUnderlined  = false; // status of underlined for previous glyph.
+    uint32_t                                     underlineChunkId = 0u;                            // give id for each chunk.
+    bool                                         isPreUnderlined  = false;                         // status of underlined for previous glyph.
+    std::map<uint32_t, UnderlineStyleProperties> mapUnderlineChunkIdWithProperties;                // mapping underlineChunkId with UnderlineStyleProperties to get properties of underlined chunk
+    UnderlineStyleProperties                     preUnderlineProperties = viewUnderlineProperties; // the previous UnderlineStyleProperties
+
+    //For septated strikethrough chunks. (this is for Markup case)
+    uint32_t                                         strikethroughChunkId = 0u;                                // give id for each chunk.
+    bool                                             isPreStrikethrough   = false;                             // status of strikethrough for previous glyph.
+    std::map<uint32_t, StrikethroughStyleProperties> mapStrikethroughChunkIdWithProperties;                    // mapping strikethroughChunkId with StrikethroughStyleProperties to get properties of strikethrough chunk
+    StrikethroughStyleProperties                     preStrikethroughProperties = viewStrikethroughProperties; // the previous StrikethroughStyleProperties
+
+    const Character*              textBuffer                = view.GetTextBuffer();
+    float                         calculatedAdvance         = 0.f;
+    const Vector<CharacterIndex>& glyphToCharacterMap       = view.GetGlyphsToCharacters();
+    const CharacterIndex*         glyphToCharacterMapBuffer = glyphToCharacterMap.Begin();
 
     //Skip hyphenIndices less than startIndexOfGlyphs or between two middle of elided text
     if(hyphenIndices)
@@ -496,6 +548,12 @@ struct AtlasRenderer::Impl
       }
     }
 
+    //To keep the last fontMetrics of lastDecorativeLinesFontId
+    FontId      lastDecorativeLinesFontId = 0; // DecorativeLines like Undeline and Strikethrough
+    FontMetrics lastDecorativeLinesFontMetrics;
+    fontClient.GetFontMetrics(lastDecorativeLinesFontId, lastDecorativeLinesFontMetrics);
+
+    // Iteration on glyphs
     for(uint32_t i = 0, glyphSize = glyphs.Size(); i < glyphSize; ++i)
     {
       GlyphInfo glyph;
@@ -510,65 +568,62 @@ struct AtlasRenderer::Impl
         glyph = *(glyphsBuffer + i);
       }
 
-      const bool isGlyphUnderlined = underlineEnabled || IsGlyphUnderlined(i, underlineRuns);
-      thereAreUnderlinedGlyphs     = thereAreUnderlinedGlyphs || isGlyphUnderlined;
-      strikethroughGlyphsExist     = strikethroughGlyphsExist || strikethroughEnabled;
+      Vector<UnderlinedGlyphRun>::ConstIterator currentUnderlinedGlyphRunIt = underlineRuns.End();
+      const bool                                isGlyphUnderlined           = underlineEnabled || IsGlyphUnderlined(i, underlineRuns, currentUnderlinedGlyphRunIt);
+      const UnderlineStyleProperties            currentUnderlineProperties  = GetCurrentUnderlineProperties(i, isGlyphUnderlined, underlineRuns, currentUnderlinedGlyphRunIt, viewUnderlineProperties);
+      float                                     currentUnderlineHeight      = currentUnderlineProperties.height;
+      thereAreUnderlinedGlyphs                                              = thereAreUnderlinedGlyphs || isGlyphUnderlined;
+
+      Vector<StrikethroughGlyphRun>::ConstIterator currentStrikethroughGlyphRunIt = strikethroughRuns.End();
+      const bool                                   isGlyphStrikethrough           = strikethroughEnabled || IsGlyphStrikethrough(i, strikethroughRuns, currentStrikethroughGlyphRunIt);
+      const StrikethroughStyleProperties           currentStrikethroughProperties = GetCurrentStrikethroughProperties(i, isGlyphStrikethrough, strikethroughRuns, currentStrikethroughGlyphRunIt, viewStrikethroughProperties);
+      float                                        currentStrikethroughHeight     = currentStrikethroughProperties.height;
+      thereAreStrikethroughGlyphs                                                 = thereAreStrikethroughGlyphs || isGlyphStrikethrough;
 
       // No operation for white space
       if(glyph.width && glyph.height)
       {
-        // Are we still using the same fontId as previous
-        if((isGlyphUnderlined || strikethroughGlyphsExist) && (glyph.fontId != lastUnderlinedFontId))
+        // Check and update decorative-lines informations
+        if(isGlyphUnderlined || isGlyphStrikethrough)
         {
-          // We need to fetch fresh font underline metrics
-          FontMetrics fontMetrics;
-          mFontClient.GetFontMetrics(glyph.fontId, fontMetrics);
-          currentUnderlinePosition = ceil(fabsf(fontMetrics.underlinePosition));
-          const float descender    = ceil(fabsf(fontMetrics.descender));
-
-          if(fabsf(underlineHeight) < Math::MACHINE_EPSILON_1000)
+          bool isDecorativeLinesFontIdUpdated = false;
+          // Are we still using the same fontId as previous
+          if(glyph.fontId != lastDecorativeLinesFontId)
           {
-            currentUnderlineThickness = fontMetrics.underlineThickness;
+            // We need to fetch fresh font metrics
+            lastDecorativeLinesFontId      = glyph.fontId;
+            isDecorativeLinesFontIdUpdated = true;
+            fontClient.GetFontMetrics(lastDecorativeLinesFontId, lastDecorativeLinesFontMetrics);
 
-            // Ensure underline will be at least a pixel high
-            if(currentUnderlineThickness < ONE)
-            {
-              currentUnderlineThickness = ONE;
-            }
-            else
+            if(isGlyphStrikethrough || isGlyphUnderlined)
             {
-              currentUnderlineThickness = ceil(currentUnderlineThickness);
+              //The currentUnderlinePosition will be used for both Underline and/or Strikethrough
+              currentUnderlinePosition = FetchUnderlinePositionFromFontMetrics(lastDecorativeLinesFontMetrics);
             }
           }
 
-          if(fabsf(strikethroughHeight) < Math::MACHINE_EPSILON_1000)
+          if(isGlyphUnderlined && (isDecorativeLinesFontIdUpdated || !(currentUnderlineProperties.IsHeightEqualTo(preUnderlineProperties))))
           {
-            // Ensure strikethrough will be at least a pixel high
-            if(currentStrikethroughThickness < ONE)
-            {
-              currentStrikethroughThickness = ONE;
-            }
-            else
+            //If the Underline Height is changed then we need to recalculate height.
+            if(!(currentUnderlineProperties.IsHeightEqualTo(preUnderlineProperties)))
             {
-              currentStrikethroughThickness = ceil(currentStrikethroughThickness);
+              maxUnderlineHeight = currentUnderlineHeight;
             }
-          }
 
-          // Clamp the underline position at the font descender and check for ( as EFL describes it ) a broken font
-          if(currentUnderlinePosition > descender)
-          {
-            currentUnderlinePosition = descender;
+            CalcualteUnderlineHeight(lastDecorativeLinesFontMetrics, currentUnderlineHeight, maxUnderlineHeight);
           }
 
-          if(fabsf(currentUnderlinePosition) < Math::MACHINE_EPSILON_1000)
+          if(isGlyphStrikethrough && (isDecorativeLinesFontIdUpdated || !(currentStrikethroughProperties.IsHeightEqualTo(preStrikethroughProperties))))
           {
-            // Move offset down by one ( EFL behavior )
-            currentUnderlinePosition = ONE;
-          }
-
-          lastUnderlinedFontId = glyph.fontId;
+            //If the Strikethrough Height is changed then we need to recalculate height.
+            if(!(currentStrikethroughProperties.IsHeightEqualTo(preStrikethroughProperties)))
+            {
+              maxStrikethroughHeight = currentStrikethroughHeight;
+            }
 
-        } // underline
+            CalcualteStrikethroughHeight(currentStrikethroughHeight, maxStrikethroughHeight);
+          }
+        } // decorative-lines
 
         AtlasGlyphManager::GlyphStyle style;
         style.isItalic = glyph.isItalicRequired;
@@ -590,7 +645,8 @@ struct AtlasRenderer::Impl
         if(addHyphen)
         {
           GlyphInfo tempInfo = *(glyphsBuffer + i);
-          position.x         = position.x + tempInfo.advance - tempInfo.xBearing + glyph.xBearing;
+          calculatedAdvance  = GetCalculatedAdvance(*(textBuffer + (*(glyphToCharacterMapBuffer + i))), characterSpacing, tempInfo.advance);
+          position.x         = position.x + calculatedAdvance - tempInfo.xBearing + glyph.xBearing;
           position.y += tempInfo.yBearing - glyph.yBearing;
         }
 
@@ -610,6 +666,18 @@ struct AtlasRenderer::Impl
           const ColorIndex colorIndex = useDefaultColor ? 0u : *(colorIndicesBuffer + i);
           const Vector4&   color      = (useDefaultColor || (0u == colorIndex)) ? defaultColor : *(colorsBuffer + colorIndex - 1u);
 
+          //The new underlined chunk. Add new id if they are not consecutive indices (this is for Markup case)
+          // Examples: "Hello <u>World</u> Hello <u>World</u>", "<u>World</u> Hello <u>World</u>", "<u>   World</u> Hello <u>World</u>"
+          if((!isPreUnderlined && isGlyphUnderlined) || (isGlyphUnderlined && (preUnderlineProperties != currentUnderlineProperties)))
+          {
+            underlineChunkId++;
+            mapUnderlineChunkIdWithProperties.insert(std::pair<uint32_t, UnderlineStyleProperties>(underlineChunkId, currentUnderlineProperties));
+          }
+
+          //Keep status of underlined for previous glyph to check consecutive indices
+          isPreUnderlined        = isGlyphUnderlined;
+          preUnderlineProperties = currentUnderlineProperties;
+
           GenerateMesh(glyph,
                        positionPlusOutlineOffset,
                        color,
@@ -617,30 +685,44 @@ struct AtlasRenderer::Impl
                        slot,
                        isGlyphUnderlined,
                        currentUnderlinePosition,
-                       currentUnderlineThickness,
+                       maxUnderlineHeight,
                        meshContainer,
                        newTextCache,
                        extents,
                        underlineChunkId,
-                       false);
+                       false,
+                       0u);
 
-          if(strikethroughGlyphsExist)
+          if(isGlyphStrikethrough)
           {
+            //The new strikethrough chunk. Add new id if they are not consecutive indices (this is for Markup case)
+            // Examples: "Hello <s>World</s> Hello <s>World</s>", "<s>World</s> Hello <s>World</s>", "<s>   World</s> Hello <s>World</s>"
+            if((!isPreStrikethrough) || (preStrikethroughProperties != currentStrikethroughProperties))
+            {
+              strikethroughChunkId++;
+              mapStrikethroughChunkIdWithProperties.insert(std::pair<uint32_t, StrikethroughStyleProperties>(strikethroughChunkId, currentStrikethroughProperties));
+            }
+
             GenerateMesh(glyph,
                          positionPlusOutlineOffset,
                          color,
                          NO_OUTLINE,
                          slot,
-                         strikethroughGlyphsExist,
+                         isGlyphStrikethrough,
                          0.0f,
-                         currentStrikethroughThickness,
+                         maxStrikethroughHeight,
                          meshContainer,
                          newTextCache,
                          strikethroughExtents,
                          0u,
-                         true);
+                         true,
+                         strikethroughChunkId);
           }
 
+          //Keep status of Strikethrough for previous glyph to check consecutive indices
+          isPreStrikethrough         = isGlyphStrikethrough;
+          preStrikethroughProperties = currentStrikethroughProperties;
+
           lastFontId = glyph.fontId; // Prevents searching for existing blocksizes when string of the same fontId.
         }
 
@@ -653,22 +735,14 @@ struct AtlasRenderer::Impl
                        slotOutline,
                        false,
                        currentUnderlinePosition,
-                       currentUnderlineThickness,
+                       maxUnderlineHeight,
                        meshContainerOutline,
                        newTextCache,
                        extents,
                        0u,
-                       false);
-        }
-
-        //The new underlined chunk. Add new id if they are not consecutive indices (this is for Markup case)
-        // Examples: "Hello <u>World</u> Hello <u>World</u>", "<u>World</u> Hello <u>World</u>", "<u>   World</u> Hello <u>World</u>"
-        if(isPreUnderlined && (isPreUnderlined != isGlyphUnderlined))
-        {
-          underlineChunkId++;
+                       false,
+                       0u);
         }
-        //Keep status of underlined for previous glyph to check consecutive indices
-        isPreUnderlined = isGlyphUnderlined;
       }
 
       if(addHyphen)
@@ -684,13 +758,13 @@ struct AtlasRenderer::Impl
     if(thereAreUnderlinedGlyphs)
     {
       // Check to see if any of the text needs an underline
-      GenerateUnderlines(meshContainer, extents, underlineColor);
+      GenerateUnderlines(meshContainer, extents, viewUnderlineProperties, mapUnderlineChunkIdWithProperties);
     }
 
-    if(strikethroughGlyphsExist)
+    if(thereAreStrikethroughGlyphs)
     {
       // Check to see if any of the text needs a strikethrough
-      GenerateStrikethrough(meshContainer, strikethroughExtents, strikethroughColor);
+      GenerateStrikethrough(meshContainer, strikethroughExtents, viewStrikethroughProperties, mapStrikethroughChunkIdWithProperties);
     }
 
     // For each MeshData object, create a mesh actor and add to the renderable actor
@@ -730,7 +804,7 @@ struct AtlasRenderer::Impl
 
     DALI_LOG_INFO(gLogFilter, Debug::Verbose, "%s\n", metrics.mVerboseGlyphCounts.c_str());
 
-    for(uint32_t i = 0; i < metrics.mAtlasMetrics.mAtlasCount; ++i)
+    for(uint32_t i = 0; gLogFilter->IsEnabledFor(Debug::Verbose) && i < metrics.mAtlasMetrics.mAtlasCount; ++i)
     {
       DALI_LOG_INFO(gLogFilter, Debug::Verbose, "   Atlas [%i] %sPixels: %s Size: %ix%i, BlockSize: %ix%i, BlocksUsed: %i/%i\n", i + 1, i > 8 ? "" : " ", metrics.mAtlasMetrics.mAtlasMetrics[i].mPixelFormat == Pixel::L8 ? "L8  " : "BGRA", metrics.mAtlasMetrics.mAtlasMetrics[i].mSize.mWidth, metrics.mAtlasMetrics.mAtlasMetrics[i].mSize.mHeight, metrics.mAtlasMetrics.mAtlasMetrics[i].mSize.mBlockWidth, metrics.mAtlasMetrics.mAtlasMetrics[i].mSize.mBlockHeight, metrics.mAtlasMetrics.mAtlasMetrics[i].mBlocksUsed, metrics.mAtlasMetrics.mAtlasMetrics[i].mTotalBlocks);
     }
@@ -832,7 +906,8 @@ struct AtlasRenderer::Impl
                       float                    lineThickness,
                       AtlasManager::AtlasSlot& slot,
                       uint32_t                 underlineChunkId,
-                      float                    strikethroughPosition)
+                      float                    strikethroughPosition,
+                      uint32_t                 strikethroughChunkId)
   {
     if(slot.mImageId)
     {
@@ -862,7 +937,8 @@ struct AtlasRenderer::Impl
                           underlinePosition,
                           lineThickness,
                           underlineChunkId,
-                          strikethroughPosition);
+                          strikethroughPosition,
+                          strikethroughChunkId);
           }
 
           return;
@@ -887,7 +963,8 @@ struct AtlasRenderer::Impl
                       underlinePosition,
                       lineThickness,
                       underlineChunkId,
-                      strikethroughPosition);
+                      strikethroughPosition,
+                      strikethroughChunkId);
       }
     }
   }
@@ -901,7 +978,8 @@ struct AtlasRenderer::Impl
                      float                    underlinePosition,
                      float                    lineThickness,
                      uint32_t                 underlineChunkId,
-                     float                    strikethroughPosition)
+                     float                    strikethroughPosition,
+                     uint32_t                 strikethroughChunkId)
   {
     bool foundExtent = false;
     for(Vector<Extent>::Iterator eIt    = extents.Begin(),
@@ -909,7 +987,7 @@ struct AtlasRenderer::Impl
         eIt != eEndIt;
         ++eIt)
     {
-      if(Equals(baseLine, eIt->mBaseLine) && underlineChunkId == eIt->mUnderlineChunkId)
+      if(Equals(baseLine, eIt->mBaseLine) && underlineChunkId == eIt->mUnderlineChunkId && strikethroughChunkId == eIt->mStrikethroughChunkId)
       {
         foundExtent = true;
         if(left < eIt->mLeft)
@@ -942,6 +1020,7 @@ struct AtlasRenderer::Impl
       extent.mUnderlineChunkId      = underlineChunkId;
       extent.mLineThickness         = lineThickness;
       extent.mStrikethroughPosition = strikethroughPosition;
+      extent.mStrikethroughChunkId  = strikethroughChunkId;
       extents.PushBack(extent);
     }
   }
@@ -984,12 +1063,14 @@ struct AtlasRenderer::Impl
     }
   }
 
-  void GenerateUnderlines(std::vector<MeshRecord>& meshRecords,
-                          Vector<Extent>&          extents,
-                          const Vector4&           underlineColor)
+  void GenerateUnderlines(std::vector<MeshRecord>&                            meshRecords,
+                          Vector<Extent>&                                     extents,
+                          const UnderlineStyleProperties&                     viewUnderlineProperties,
+                          const std::map<uint32_t, UnderlineStyleProperties>& mapUnderlineChunkIdWithProperties)
   {
     AtlasManager::Mesh2D newMesh;
     unsigned short       faceIndex = 0;
+
     for(Vector<Extent>::ConstIterator eIt    = extents.Begin(),
                                       eEndIt = extents.End();
         eIt != eEndIt;
@@ -999,56 +1080,174 @@ struct AtlasRenderer::Impl
       uint32_t               index = eIt->mMeshRecordIndex;
       Vector2                uv    = mGlyphManager.GetAtlasSize(meshRecords[index].mAtlasId);
 
-      // Make sure we don't hit texture edge for single pixel texture ( filled pixel is in top left of every atlas )
-      float u         = HALF / uv.x;
-      float v         = HALF / uv.y;
-      float thickness = eIt->mLineThickness;
-      float baseLine  = eIt->mBaseLine + eIt->mUnderlinePosition - (thickness * HALF);
-      float tlx       = eIt->mLeft;
-      float brx       = eIt->mRight;
-
-      vert.mPosition.x  = tlx;
-      vert.mPosition.y  = baseLine;
-      vert.mTexCoords.x = ZERO;
-      vert.mTexCoords.y = ZERO;
-      vert.mColor       = underlineColor;
-      newMesh.mVertices.PushBack(vert);
-
-      vert.mPosition.x  = brx;
-      vert.mPosition.y  = baseLine;
-      vert.mTexCoords.x = u;
-      vert.mColor       = underlineColor;
-      newMesh.mVertices.PushBack(vert);
+      auto pairUnderlineChunkIdWithProperties = mapUnderlineChunkIdWithProperties.find(eIt->mUnderlineChunkId);
 
-      vert.mPosition.x  = tlx;
-      vert.mPosition.y  = baseLine + thickness;
-      vert.mTexCoords.x = ZERO;
-      vert.mTexCoords.y = v;
-      vert.mColor       = underlineColor;
-      newMesh.mVertices.PushBack(vert);
+      const UnderlineStyleProperties underlineProperties = (pairUnderlineChunkIdWithProperties == mapUnderlineChunkIdWithProperties.end())
+                                                             ? viewUnderlineProperties
+                                                             : pairUnderlineChunkIdWithProperties->second;
 
-      vert.mPosition.x  = brx;
-      vert.mPosition.y  = baseLine + thickness;
-      vert.mTexCoords.x = u;
-      vert.mColor       = underlineColor;
-      newMesh.mVertices.PushBack(vert);
+      const Vector4&               underlineColor       = underlineProperties.colorDefined ? underlineProperties.color : viewUnderlineProperties.color;
+      const Text::Underline::Type& underlineType        = underlineProperties.typeDefined ? underlineProperties.type : viewUnderlineProperties.type;
+      const float&                 dashedUnderlineGap   = underlineProperties.dashGapDefined ? underlineProperties.dashGap : viewUnderlineProperties.dashGap;
+      const float&                 dashedUnderlineWidth = underlineProperties.dashWidthDefined ? underlineProperties.dashWidth : viewUnderlineProperties.dashWidth;
 
-      // Six indices in counter clockwise winding
-      newMesh.mIndices.PushBack(faceIndex + 1u);
-      newMesh.mIndices.PushBack(faceIndex);
-      newMesh.mIndices.PushBack(faceIndex + 2u);
-      newMesh.mIndices.PushBack(faceIndex + 2u);
-      newMesh.mIndices.PushBack(faceIndex + 3u);
-      newMesh.mIndices.PushBack(faceIndex + 1u);
-      faceIndex += 4;
+      // Make sure we don't hit texture edge for single pixel texture ( filled pixel is in top left of every atlas )
+      float u           = HALF / uv.x;
+      float v           = HALF / uv.y;
+      float thickness   = eIt->mLineThickness;
+      float ShiftLineBy = (underlineType == Text::Underline::Type::DOUBLE) ? (thickness * ONE_AND_A_HALF) : (thickness * HALF);
+      float baseLine    = eIt->mBaseLine + eIt->mUnderlinePosition - ShiftLineBy;
+      float tlx         = eIt->mLeft;
+      float brx         = eIt->mRight;
+
+      if(underlineType == Text::Underline::Type::DASHED)
+      {
+        float dashTlx = tlx;
+        float dashBrx = tlx;
 
-      Toolkit::Internal::AtlasMeshFactory::AppendMesh(meshRecords[index].mMesh, newMesh);
+        while((dashTlx >= tlx) && (dashTlx < brx) && ((dashTlx + dashedUnderlineWidth) <= brx))
+        {
+          dashBrx = dashTlx + dashedUnderlineWidth;
+
+          //The top left edge of the underline
+          vert.mPosition.x  = dashTlx;
+          vert.mPosition.y  = baseLine;
+          vert.mTexCoords.x = ZERO;
+          vert.mTexCoords.y = ZERO;
+          vert.mColor       = underlineColor;
+          newMesh.mVertices.PushBack(vert);
+
+          //The top right edge of the underline
+          vert.mPosition.x  = dashBrx;
+          vert.mPosition.y  = baseLine;
+          vert.mTexCoords.x = u;
+          vert.mColor       = underlineColor;
+          newMesh.mVertices.PushBack(vert);
+
+          //The bottom left edge of the underline
+          vert.mPosition.x  = dashTlx;
+          vert.mPosition.y  = baseLine + thickness;
+          vert.mTexCoords.x = ZERO;
+          vert.mTexCoords.y = v;
+          vert.mColor       = underlineColor;
+          newMesh.mVertices.PushBack(vert);
+
+          //The bottom right edge of the underline
+          vert.mPosition.x  = dashBrx;
+          vert.mPosition.y  = baseLine + thickness;
+          vert.mTexCoords.x = u;
+          vert.mColor       = underlineColor;
+          newMesh.mVertices.PushBack(vert);
+
+          dashTlx = dashBrx + dashedUnderlineGap; // The next dash will start at the right of the current dash plus the gap
+
+          // Six indices in counter clockwise winding
+          newMesh.mIndices.PushBack(faceIndex + 1u);
+          newMesh.mIndices.PushBack(faceIndex);
+          newMesh.mIndices.PushBack(faceIndex + 2u);
+          newMesh.mIndices.PushBack(faceIndex + 2u);
+          newMesh.mIndices.PushBack(faceIndex + 3u);
+          newMesh.mIndices.PushBack(faceIndex + 1u);
+
+          faceIndex += 4;
+
+          Toolkit::Internal::AtlasMeshFactory::AppendMesh(meshRecords[index].mMesh, newMesh);
+        }
+      }
+      else
+      {
+        // It's either SOLID or DOUBLE so we need to generate the first solid underline anyway.
+        vert.mPosition.x  = tlx;
+        vert.mPosition.y  = baseLine;
+        vert.mTexCoords.x = ZERO;
+        vert.mTexCoords.y = ZERO;
+        vert.mColor       = underlineColor;
+        newMesh.mVertices.PushBack(vert);
+
+        vert.mPosition.x  = brx;
+        vert.mPosition.y  = baseLine;
+        vert.mTexCoords.x = u;
+        vert.mColor       = underlineColor;
+        newMesh.mVertices.PushBack(vert);
+
+        vert.mPosition.x  = tlx;
+        vert.mPosition.y  = baseLine + thickness;
+        vert.mTexCoords.x = ZERO;
+        vert.mTexCoords.y = v;
+        vert.mColor       = underlineColor;
+        newMesh.mVertices.PushBack(vert);
+
+        vert.mPosition.x  = brx;
+        vert.mPosition.y  = baseLine + thickness;
+        vert.mTexCoords.x = u;
+        vert.mColor       = underlineColor;
+        newMesh.mVertices.PushBack(vert);
+
+        // Six indices in counter clockwise winding
+        newMesh.mIndices.PushBack(faceIndex + 1u);
+        newMesh.mIndices.PushBack(faceIndex);
+        newMesh.mIndices.PushBack(faceIndex + 2u);
+        newMesh.mIndices.PushBack(faceIndex + 2u);
+        newMesh.mIndices.PushBack(faceIndex + 3u);
+        newMesh.mIndices.PushBack(faceIndex + 1u);
+        faceIndex += 4;
+
+        Toolkit::Internal::AtlasMeshFactory::AppendMesh(meshRecords[index].mMesh, newMesh);
+
+        if(underlineType == Text::Underline::Type::DOUBLE)
+        {
+          baseLine += 2 * thickness;
+
+          //The top left edge of the underline
+          vert.mPosition.x  = tlx;
+          vert.mPosition.y  = baseLine; // Vertical start of the second underline
+          vert.mTexCoords.x = ZERO;
+          vert.mTexCoords.y = ZERO;
+          vert.mColor       = underlineColor;
+          newMesh.mVertices.PushBack(vert);
+
+          //The top right edge of the underline
+          vert.mPosition.x  = brx;
+          vert.mPosition.y  = baseLine;
+          vert.mTexCoords.x = u;
+          vert.mColor       = underlineColor;
+          newMesh.mVertices.PushBack(vert);
+
+          //The bottom left edge of the underline
+          vert.mPosition.x  = tlx;
+          vert.mPosition.y  = baseLine + thickness; // Vertical End of the second underline
+          vert.mTexCoords.x = ZERO;
+          vert.mTexCoords.y = v;
+          vert.mColor       = underlineColor;
+          newMesh.mVertices.PushBack(vert);
+
+          //The bottom right edge of the underline
+          vert.mPosition.x  = brx;
+          vert.mPosition.y  = baseLine + thickness;
+          vert.mTexCoords.x = u;
+          vert.mColor       = underlineColor;
+          newMesh.mVertices.PushBack(vert);
+
+          // Six indices in counter clockwise winding
+          newMesh.mIndices.PushBack(faceIndex + 1u);
+          newMesh.mIndices.PushBack(faceIndex);
+          newMesh.mIndices.PushBack(faceIndex + 2u);
+          newMesh.mIndices.PushBack(faceIndex + 2u);
+          newMesh.mIndices.PushBack(faceIndex + 3u);
+          newMesh.mIndices.PushBack(faceIndex + 1u);
+
+          faceIndex += 4;
+
+          Toolkit::Internal::AtlasMeshFactory::AppendMesh(meshRecords[index].mMesh, newMesh);
+        }
+      }
     }
   }
 
-  void GenerateStrikethrough(std::vector<MeshRecord>& meshRecords,
-                             Vector<Extent>&          extents,
-                             const Vector4&           strikethroughColor)
+  void GenerateStrikethrough(std::vector<MeshRecord>&                                meshRecords,
+                             Vector<Extent>&                                         extents,
+                             const StrikethroughStyleProperties&                     viewStrikethroughProperties,
+                             const std::map<uint32_t, StrikethroughStyleProperties>& mapStrikethroughChunkIdWithProperties)
   {
     AtlasManager::Mesh2D newMesh;
     unsigned short       faceIndex = 0;
@@ -1061,6 +1260,14 @@ struct AtlasRenderer::Impl
       uint32_t               index = eIt->mMeshRecordIndex;
       Vector2                uv    = mGlyphManager.GetAtlasSize(meshRecords[index].mAtlasId);
 
+      auto pairStrikethroughChunkIdWithProperties = mapStrikethroughChunkIdWithProperties.find(eIt->mStrikethroughChunkId);
+
+      const StrikethroughStyleProperties strikethroughProperties = (pairStrikethroughChunkIdWithProperties == mapStrikethroughChunkIdWithProperties.end())
+                                                                     ? viewStrikethroughProperties
+                                                                     : pairStrikethroughChunkIdWithProperties->second;
+
+      const Vector4& strikethroughColor = strikethroughProperties.colorDefined ? strikethroughProperties.color : viewStrikethroughProperties.color;
+
       // Make sure we don't hit texture edge for single pixel texture ( filled pixel is in top left of every atlas )
       float u                     = HALF / uv.x;
       float v                     = HALF / uv.y;