Extending Text Styles - Adding Dashed/Double Underline
[platform/core/uifw/dali-toolkit.git] / dali-toolkit / internal / text / rendering / atlas / text-atlas-renderer.cpp
index 208460c..a210785 100644 (file)
@@ -46,11 +46,8 @@ 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 uint32_t DEFAULT_ATLAS_WIDTH  = 512u;
-const uint32_t DEFAULT_ATLAS_HEIGHT = 512u;
-const uint32_t MAX_ATLAS_WIDTH  = 1024u;
-const uint32_t MAX_ATLAS_HEIGHT = 1024u;
-const uint32_t DOUBLE_PIXEL_PADDING = 4u;//Padding will be added twice to Atlas
+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
 
@@ -74,7 +71,7 @@ struct AtlasRenderer::Impl
   };
 
   /**
-   * brief Struct used to generate the underline mesh.
+   * brief Struct used to generate the underline/striketthrough mesh.
    * There is one Extent per line of text.
    */
   struct Extent
@@ -84,8 +81,10 @@ struct AtlasRenderer::Impl
       mLeft(0.0f),
       mRight(0.0f),
       mUnderlinePosition(0.0f),
-      mUnderlineThickness(0.0f),
-      mMeshRecordIndex(0u)
+      mLineThickness(0.0f),
+      mMeshRecordIndex(0u),
+      mUnderlineChunkId(0u),
+      mStrikethroughPosition(0.0f)
     {
     }
 
@@ -93,8 +92,10 @@ struct AtlasRenderer::Impl
     float    mLeft;
     float    mRight;
     float    mUnderlinePosition;
-    float    mUnderlineThickness;
+    float    mLineThickness;
     uint32_t mMeshRecordIndex;
+    uint32_t mUnderlineChunkId;
+    float    mStrikethroughPosition;
   };
 
   struct MaxBlockSize
@@ -175,6 +176,9 @@ struct AtlasRenderer::Impl
 
   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.
+    const Size& maximumTextAtlasSize = mFontClient.GetMaximumTextAtlasSize(); //Retrieve maximum size of text-atlas-block from font-client.
+
     const bool glyphNotCached = !mGlyphManager.IsCached(glyph.fontId, glyph.index, style, slot); // Check FontGlyphRecord vector for entry with glyph index and fontId
 
     DALI_LOG_INFO(gLogFilter, Debug::Verbose, "AddGlyphs fontID[%u] glyphIndex[%u] [%s]\n", glyph.fontId, glyph.index, (glyphNotCached) ? "not cached" : "cached");
@@ -248,15 +252,14 @@ struct AtlasRenderer::Impl
           // If CheckAtlas in AtlasManager::Add can't fit the bitmap in the current atlas it will create a new atlas
 
           // Setting the block size and size of new atlas does not mean a new one will be created. An existing atlas may still surffice.
-          uint32_t default_width = DEFAULT_ATLAS_WIDTH;
-          uint32_t default_height = DEFAULT_ATLAS_HEIGHT;
+          uint32_t default_width  = defaultTextAtlasSize.width;
+          uint32_t default_height = defaultTextAtlasSize.height;
 
-          while (
+          while(
             (blockSize.mNeededBlockWidth >= (default_width - (DOUBLE_PIXEL_PADDING + 1u)) ||
-             blockSize.mNeededBlockHeight >= (default_height - (DOUBLE_PIXEL_PADDING + 1u)))
-            &&
-            (default_width < MAX_ATLAS_WIDTH &&
-             default_height < MAX_ATLAS_HEIGHT))
+             blockSize.mNeededBlockHeight >= (default_height - (DOUBLE_PIXEL_PADDING + 1u))) &&
+            (default_width < maximumTextAtlasSize.width &&
+             default_height < maximumTextAtlasSize.height))
           {
             default_width <<= 1u;
             default_height <<= 1u;
@@ -283,26 +286,31 @@ struct AtlasRenderer::Impl
                     const Vector4&           color,
                     uint16_t                 outline,
                     AtlasManager::AtlasSlot& slot,
-                    bool                     underlineGlyph,
+                    bool                     decorationlineGlyph,
                     float                    currentUnderlinePosition,
-                    float                    currentUnderlineThickness,
+                    float                    currentlineThickness,
                     std::vector<MeshRecord>& meshContainer,
                     Vector<TextCacheEntry>&  newTextCache,
-                    Vector<Extent>&          extents)
+                    Vector<Extent>&          extents,
+                    uint32_t                 underlineChunkId,
+                    bool                     isGlyphCached)
   {
     // Generate mesh data for this quad, plugging in our supplied position
     AtlasManager::Mesh2D newMesh;
     mGlyphManager.GenerateMeshData(slot.mImageId, position, newMesh);
 
-    TextCacheEntry textCacheEntry;
-    textCacheEntry.mFontId       = glyph.fontId;
-    textCacheEntry.mImageId      = slot.mImageId;
-    textCacheEntry.mIndex        = glyph.index;
-    textCacheEntry.mOutlineWidth = outline;
-    textCacheEntry.isItalic      = glyph.isItalicRequired;
-    textCacheEntry.isBold        = glyph.isBoldRequired;
-
-    newTextCache.PushBack(textCacheEntry);
+    if(!isGlyphCached)
+    {
+      TextCacheEntry textCacheEntry;
+      textCacheEntry.mFontId       = glyph.fontId;
+      textCacheEntry.mImageId      = slot.mImageId;
+      textCacheEntry.mIndex        = glyph.index;
+      textCacheEntry.mOutlineWidth = outline;
+      textCacheEntry.isItalic      = glyph.isItalicRequired;
+      textCacheEntry.isBold        = glyph.isBoldRequired;
+
+      newTextCache.PushBack(textCacheEntry);
+    }
 
     AtlasManager::Vertex2D* verticesBuffer = newMesh.mVertices.Begin();
 
@@ -321,10 +329,12 @@ struct AtlasRenderer::Impl
                    newMesh,
                    extents,
                    position.y + glyph.yBearing,
-                   underlineGlyph,
+                   decorationlineGlyph,
                    currentUnderlinePosition,
-                   currentUnderlineThickness,
-                   slot);
+                   currentlineThickness,
+                   slot,
+                   underlineChunkId,
+                   position.y + (glyph.height * HALF));
   }
 
   void CreateActors(const std::vector<MeshRecord>& meshContainer,
@@ -414,18 +424,33 @@ struct AtlasRenderer::Impl
     std::vector<MeshRecord> meshContainer;
     std::vector<MeshRecord> meshContainerOutline;
     Vector<Extent>          extents;
+    Vector<Extent>          strikethroughExtents;
     mDepth = depth;
 
-    const Vector2& textSize(view.GetLayoutSize());
-    const Vector2  halfTextSize(textSize * 0.5f);
-    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 Vector4& outlineColor(view.GetOutlineColor());
-    const bool     isOutline = 0u != outlineWidth;
+    const Vector2&              textSize(view.GetLayoutSize());
+    const Vector2               halfTextSize(textSize * 0.5f);
+    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 Text::Underline::Type underlineType        = view.GetUnderlineType();
+    const float                 dashedUnderlineWidth = view.GetDashedUnderlineWidth();
+    const float                 dashedUnderlineGap   = view.GetDashedUnderlineGap();
+    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();
+
+    // Elided text info. Indices according to elided text.
+    const auto startIndexOfGlyphs              = view.GetStartIndexOfElidedGlyphs();
+    const auto firstMiddleIndexOfElidedGlyphs  = view.GetFirstMiddleIndexOfElidedGlyphs();
+    const auto secondMiddleIndexOfElidedGlyphs = view.GetSecondMiddleIndexOfElidedGlyphs();
 
     const bool useDefaultColor = (NULL == colorsBuffer);
 
@@ -438,12 +463,14 @@ struct AtlasRenderer::Impl
                           numberOfUnderlineRuns);
 
     bool thereAreUnderlinedGlyphs = false;
+    bool strikethroughGlyphsExist = false;
 
-    float  currentUnderlinePosition  = ZERO;
-    float  currentUnderlineThickness = underlineHeight;
-    FontId lastFontId                = 0;
-    FontId lastUnderlinedFontId      = 0;
-    Style  style                     = STYLE_NORMAL;
+    float  currentUnderlinePosition      = ZERO;
+    float  currentUnderlineThickness     = underlineHeight;
+    float  currentStrikethroughThickness = strikethroughHeight;
+    FontId lastFontId                    = 0;
+    FontId lastUnderlinedFontId          = 0;
+    Style  style                         = STYLE_NORMAL;
 
     if(fabsf(shadowOffset.x) > Math::MACHINE_EPSILON_1 || fabsf(shadowOffset.y) > Math::MACHINE_EPSILON_1)
     {
@@ -457,18 +484,45 @@ struct AtlasRenderer::Impl
     const GlyphInfo* const glyphsBuffer    = glyphs.Begin();
     const Vector2* const   positionsBuffer = positions.Begin();
     const Vector2          lineOffsetPosition(minLineOffset, 0.f);
+    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.
+
+    //Skip hyphenIndices less than startIndexOfGlyphs or between two middle of elided text
+    if(hyphenIndices)
+    {
+      while((hyphenIndex < hyphensCount) && (hyphenIndices[hyphenIndex] < startIndexOfGlyphs ||
+                                             (hyphenIndices[hyphenIndex] > firstMiddleIndexOfElidedGlyphs && hyphenIndices[hyphenIndex] < secondMiddleIndexOfElidedGlyphs)))
+      {
+        ++hyphenIndex;
+      }
+    }
 
     for(uint32_t i = 0, glyphSize = glyphs.Size(); i < glyphSize; ++i)
     {
-      const GlyphInfo& glyph             = *(glyphsBuffer + i);
-      const bool       isGlyphUnderlined = underlineEnabled || IsGlyphUnderlined(i, underlineRuns);
-      thereAreUnderlinedGlyphs           = thereAreUnderlinedGlyphs || isGlyphUnderlined;
+      GlyphInfo glyph;
+      bool      addHyphen = ((hyphenIndex < hyphensCount) && hyphenIndices && ((i + startIndexOfGlyphs) == hyphenIndices[hyphenIndex]));
+      if(addHyphen && hyphens)
+      {
+        glyph = hyphens[hyphenIndex];
+        i--;
+      }
+      else
+      {
+        glyph = *(glyphsBuffer + i);
+      }
+
+      const bool isGlyphUnderlined = underlineEnabled || IsGlyphUnderlined(i, underlineRuns);
+      thereAreUnderlinedGlyphs     = thereAreUnderlinedGlyphs || isGlyphUnderlined;
+      strikethroughGlyphsExist     = strikethroughGlyphsExist || strikethroughEnabled;
 
       // No operation for white space
       if(glyph.width && glyph.height)
       {
         // Are we still using the same fontId as previous
-        if(isGlyphUnderlined && (glyph.fontId != lastUnderlinedFontId))
+        if((isGlyphUnderlined || strikethroughGlyphsExist) && (glyph.fontId != lastUnderlinedFontId))
         {
           // We need to fetch fresh font underline metrics
           FontMetrics fontMetrics;
@@ -491,6 +545,19 @@ struct AtlasRenderer::Impl
             }
           }
 
+          if(fabsf(strikethroughHeight) < Math::MACHINE_EPSILON_1000)
+          {
+            // Ensure strikethrough will be at least a pixel high
+            if(currentStrikethroughThickness < ONE)
+            {
+              currentStrikethroughThickness = ONE;
+            }
+            else
+            {
+              currentStrikethroughThickness = ceil(currentStrikethroughThickness);
+            }
+          }
+
           // Clamp the underline position at the font descender and check for ( as EFL describes it ) a broken font
           if(currentUnderlinePosition > descender)
           {
@@ -504,6 +571,7 @@ struct AtlasRenderer::Impl
           }
 
           lastUnderlinedFontId = glyph.fontId;
+
         } // underline
 
         AtlasGlyphManager::GlyphStyle style;
@@ -521,8 +589,16 @@ struct AtlasRenderer::Impl
         }
 
         // Move the origin (0,0) of the mesh to the center of the actor
-        const Vector2& temp     = *(positionsBuffer + i);
-        const Vector2  position = Vector2(roundf(temp.x), temp.y) - halfTextSize - lineOffsetPosition; // roundf() avoids pixel alignment issues.
+        Vector2 position = *(positionsBuffer + i);
+
+        if(addHyphen)
+        {
+          GlyphInfo tempInfo = *(glyphsBuffer + i);
+          position.x         = position.x + tempInfo.advance - tempInfo.xBearing + glyph.xBearing;
+          position.y += tempInfo.yBearing - glyph.yBearing;
+        }
+
+        position = Vector2(roundf(position.x), position.y) - halfTextSize - lineOffsetPosition; // roundf() avoids pixel alignment issues.
 
         if(0u != slot.mImageId) // invalid slot id, glyph has failed to be added to atlas
         {
@@ -548,7 +624,26 @@ struct AtlasRenderer::Impl
                        currentUnderlineThickness,
                        meshContainer,
                        newTextCache,
-                       extents);
+                       extents,
+                       underlineChunkId,
+                       false);
+
+          if(strikethroughGlyphsExist)
+          {
+            GenerateMesh(glyph,
+                         positionPlusOutlineOffset,
+                         color,
+                         NO_OUTLINE,
+                         slot,
+                         strikethroughGlyphsExist,
+                         0.0f,
+                         currentStrikethroughThickness,
+                         meshContainer,
+                         newTextCache,
+                         strikethroughExtents,
+                         0u,
+                         true);
+          }
 
           lastFontId = glyph.fontId; // Prevents searching for existing blocksizes when string of the same fontId.
         }
@@ -565,8 +660,24 @@ struct AtlasRenderer::Impl
                        currentUnderlineThickness,
                        meshContainerOutline,
                        newTextCache,
-                       extents);
+                       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++;
+        }
+        //Keep status of underlined for previous glyph to check consecutive indices
+        isPreUnderlined = isGlyphUnderlined;
+      }
+
+      if(addHyphen)
+      {
+        hyphenIndex++;
       }
     } // glyphs
 
@@ -577,7 +688,13 @@ struct AtlasRenderer::Impl
     if(thereAreUnderlinedGlyphs)
     {
       // Check to see if any of the text needs an underline
-      GenerateUnderlines(meshContainer, extents, underlineColor);
+      GenerateUnderlines(meshContainer, extents, underlineColor, underlineType, dashedUnderlineWidth, dashedUnderlineGap);
+    }
+
+    if(strikethroughGlyphsExist)
+    {
+      // Check to see if any of the text needs a strikethrough
+      GenerateStrikethrough(meshContainer, strikethroughExtents, strikethroughColor);
     }
 
     // For each MeshData object, create a mesh actor and add to the renderable actor
@@ -714,10 +831,12 @@ struct AtlasRenderer::Impl
                       AtlasManager::Mesh2D&    newMesh,
                       Vector<Extent>&          extents,
                       float                    baseLine,
-                      bool                     underlineGlyph,
+                      bool                     decorationlineGlyph,
                       float                    underlinePosition,
-                      float                    underlineThickness,
-                      AtlasManager::AtlasSlot& slot)
+                      float                    lineThickness,
+                      AtlasManager::AtlasSlot& slot,
+                      uint32_t                 underlineChunkId,
+                      float                    strikethroughPosition)
   {
     if(slot.mImageId)
     {
@@ -736,7 +855,7 @@ struct AtlasRenderer::Impl
           // Append the mesh to the existing mesh and adjust any extents
           Toolkit::Internal::AtlasMeshFactory::AppendMesh(mIt->mMesh, newMesh);
 
-          if(underlineGlyph)
+          if(decorationlineGlyph)
           {
             AdjustExtents(extents,
                           meshContainer,
@@ -745,7 +864,9 @@ struct AtlasRenderer::Impl
                           right,
                           baseLine,
                           underlinePosition,
-                          underlineThickness);
+                          lineThickness,
+                          underlineChunkId,
+                          strikethroughPosition);
           }
 
           return;
@@ -758,7 +879,7 @@ struct AtlasRenderer::Impl
       meshRecord.mMesh    = newMesh;
       meshContainer.push_back(meshRecord);
 
-      if(underlineGlyph)
+      if(decorationlineGlyph)
       {
         // Adjust extents for this new meshrecord
         AdjustExtents(extents,
@@ -768,7 +889,9 @@ struct AtlasRenderer::Impl
                       right,
                       baseLine,
                       underlinePosition,
-                      underlineThickness);
+                      lineThickness,
+                      underlineChunkId,
+                      strikethroughPosition);
       }
     }
   }
@@ -780,7 +903,9 @@ struct AtlasRenderer::Impl
                      float                    right,
                      float                    baseLine,
                      float                    underlinePosition,
-                     float                    underlineThickness)
+                     float                    lineThickness,
+                     uint32_t                 underlineChunkId,
+                     float                    strikethroughPosition)
   {
     bool foundExtent = false;
     for(Vector<Extent>::Iterator eIt    = extents.Begin(),
@@ -788,7 +913,7 @@ struct AtlasRenderer::Impl
         eIt != eEndIt;
         ++eIt)
     {
-      if(Equals(baseLine, eIt->mBaseLine))
+      if(Equals(baseLine, eIt->mBaseLine) && underlineChunkId == eIt->mUnderlineChunkId)
       {
         foundExtent = true;
         if(left < eIt->mLeft)
@@ -804,21 +929,23 @@ struct AtlasRenderer::Impl
         {
           eIt->mUnderlinePosition = underlinePosition;
         }
-        if(underlineThickness > eIt->mUnderlineThickness)
+        if(lineThickness > eIt->mLineThickness)
         {
-          eIt->mUnderlineThickness = underlineThickness;
+          eIt->mLineThickness = lineThickness;
         }
       }
     }
     if(!foundExtent)
     {
       Extent extent;
-      extent.mLeft               = left;
-      extent.mRight              = right;
-      extent.mBaseLine           = baseLine;
-      extent.mUnderlinePosition  = underlinePosition;
-      extent.mUnderlineThickness = underlineThickness;
-      extent.mMeshRecordIndex    = index;
+      extent.mLeft                  = left;
+      extent.mRight                 = right;
+      extent.mBaseLine              = baseLine;
+      extent.mUnderlinePosition     = underlinePosition;
+      extent.mMeshRecordIndex       = index;
+      extent.mUnderlineChunkId      = underlineChunkId;
+      extent.mLineThickness         = lineThickness;
+      extent.mStrikethroughPosition = strikethroughPosition;
       extents.PushBack(extent);
     }
   }
@@ -861,9 +988,182 @@ 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 Vector4&               underlineColor,
+                          const Text::Underline::Type& underlineType,
+                          const float&                 dashedUnderlineWidth,
+                          const float&                 dashedUnderlineGap)
+  {
+    AtlasManager::Mesh2D newMesh;
+    unsigned short       faceIndex = 0;
+    for(Vector<Extent>::ConstIterator eIt    = extents.Begin(),
+                                      eEndIt = extents.End();
+        eIt != eEndIt;
+        ++eIt)
+    {
+      AtlasManager::Vertex2D vert;
+      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 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;
+        faceIndex     = 0;
+
+        AtlasManager::Mesh2D 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)
   {
     AtlasManager::Mesh2D newMesh;
     unsigned short       faceIndex = 0;
@@ -877,37 +1177,37 @@ struct AtlasRenderer::Impl
       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->mUnderlineThickness;
-      float baseLine  = eIt->mBaseLine + eIt->mUnderlinePosition - (thickness * HALF);
-      float tlx       = eIt->mLeft;
-      float brx       = eIt->mRight;
+      float u                     = HALF / uv.x;
+      float v                     = HALF / uv.y;
+      float thickness             = eIt->mLineThickness;
+      float tlx                   = eIt->mLeft;
+      float brx                   = eIt->mRight;
+      float strikethroughPosition = eIt->mStrikethroughPosition;
 
       vert.mPosition.x  = tlx;
-      vert.mPosition.y  = baseLine;
+      vert.mPosition.y  = strikethroughPosition;
       vert.mTexCoords.x = ZERO;
       vert.mTexCoords.y = ZERO;
-      vert.mColor       = underlineColor;
+      vert.mColor       = strikethroughColor;
       newMesh.mVertices.PushBack(vert);
 
       vert.mPosition.x  = brx;
-      vert.mPosition.y  = baseLine;
+      vert.mPosition.y  = strikethroughPosition;
       vert.mTexCoords.x = u;
-      vert.mColor       = underlineColor;
+      vert.mColor       = strikethroughColor;
       newMesh.mVertices.PushBack(vert);
 
       vert.mPosition.x  = tlx;
-      vert.mPosition.y  = baseLine + thickness;
+      vert.mPosition.y  = strikethroughPosition + thickness;
       vert.mTexCoords.x = ZERO;
       vert.mTexCoords.y = v;
-      vert.mColor       = underlineColor;
+      vert.mColor       = strikethroughColor;
       newMesh.mVertices.PushBack(vert);
 
       vert.mPosition.x  = brx;
-      vert.mPosition.y  = baseLine + thickness;
+      vert.mPosition.y  = strikethroughPosition + thickness;
       vert.mTexCoords.x = u;
-      vert.mColor       = underlineColor;
+      vert.mColor       = strikethroughColor;
       newMesh.mVertices.PushBack(vert);
 
       // Six indices in counter clockwise winding