Resolve incorrect position for Ellipsis when mixed LTR & RTL languages and set layout...
[platform/core/uifw/dali-toolkit.git] / dali-toolkit / internal / text / rendering / view-model.cpp
index f695cd2..9cfba93 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.
@@ -23,6 +23,7 @@
 #include <memory.h>
 
 // INTERNAL INCLUDES
+#include <dali-toolkit/internal/text/glyph-metrics-helper.h>
 #include <dali-toolkit/internal/text/line-run.h>
 
 namespace Dali
@@ -35,7 +36,11 @@ ViewModel::ViewModel(const ModelInterface* const model)
 : mModel(model),
   mElidedGlyphs(),
   mElidedLayout(),
-  mIsTextElided(false)
+  mIsTextElided(false),
+  mStartIndexOfElidedGlyphs(0u),
+  mEndIndexOfElidedGlyphs(0u),
+  mFirstMiddleIndexOfElidedGlyphs(0u),
+  mSecondMiddleIndexOfElidedGlyphs(0u)
 {
 }
 
@@ -73,6 +78,11 @@ DevelText::VerticalLineAlignment::Type ViewModel::GetVerticalLineAlignment() con
   return mModel->GetVerticalLineAlignment();
 }
 
+DevelText::EllipsisPosition::Type ViewModel::GetEllipsisPosition() const
+{
+  return mModel->GetEllipsisPosition();
+}
+
 bool ViewModel::IsTextElideEnabled() const
 {
   return mModel->IsTextElideEnabled();
@@ -112,6 +122,46 @@ Length ViewModel::GetNumberOfGlyphs() const
   return 0u;
 }
 
+GlyphIndex ViewModel::GetStartIndexOfElidedGlyphs() const
+{
+  if(mIsTextElided && mModel->IsTextElideEnabled())
+  {
+    return mStartIndexOfElidedGlyphs;
+  }
+
+  return mModel->GetStartIndexOfElidedGlyphs();
+}
+
+GlyphIndex ViewModel::GetEndIndexOfElidedGlyphs() const
+{
+  if(mIsTextElided && mModel->IsTextElideEnabled())
+  {
+    return mEndIndexOfElidedGlyphs;
+  }
+
+  return mModel->GetEndIndexOfElidedGlyphs();
+}
+
+GlyphIndex ViewModel::GetFirstMiddleIndexOfElidedGlyphs() const
+{
+  if(mIsTextElided && mModel->IsTextElideEnabled())
+  {
+    return mFirstMiddleIndexOfElidedGlyphs;
+  }
+
+  return mModel->GetFirstMiddleIndexOfElidedGlyphs();
+}
+
+GlyphIndex ViewModel::GetSecondMiddleIndexOfElidedGlyphs() const
+{
+  if(mIsTextElided && mModel->IsTextElideEnabled())
+  {
+    return mSecondMiddleIndexOfElidedGlyphs;
+  }
+
+  return mModel->GetSecondMiddleIndexOfElidedGlyphs();
+}
+
 const GlyphInfo* const ViewModel::GetGlyphs() const
 {
   if(mIsTextElided && mModel->IsTextElideEnabled())
@@ -160,6 +210,11 @@ const ColorIndex* const ViewModel::GetBackgroundColorIndices() const
   return mModel->GetBackgroundColorIndices();
 }
 
+bool const ViewModel::IsMarkupBackgroundColorSet() const
+{
+  return mModel->IsMarkupBackgroundColorSet();
+}
+
 const Vector4& ViewModel::GetDefaultColor() const
 {
   return mModel->GetDefaultColor();
@@ -195,12 +250,27 @@ float ViewModel::GetUnderlineHeight() const
   return mModel->GetUnderlineHeight();
 }
 
+Text::Underline::Type ViewModel::GetUnderlineType() const
+{
+  return mModel->GetUnderlineType();
+}
+
+float ViewModel::GetDashedUnderlineWidth() const
+{
+  return mModel->GetDashedUnderlineWidth();
+}
+
+float ViewModel::GetDashedUnderlineGap() const
+{
+  return mModel->GetDashedUnderlineGap();
+}
+
 Length ViewModel::GetNumberOfUnderlineRuns() const
 {
   return mModel->GetNumberOfUnderlineRuns();
 }
 
-void ViewModel::GetUnderlineRuns(GlyphRun* underlineRuns, UnderlineRunIndex index, Length numberOfRuns) const
+void ViewModel::GetUnderlineRuns(UnderlinedGlyphRun* underlineRuns, UnderlineRunIndex index, Length numberOfRuns) const
 {
   mModel->GetUnderlineRuns(underlineRuns, index, numberOfRuns);
 }
@@ -245,164 +315,420 @@ Length ViewModel::GetHyphensCount() const
   return mModel->GetHyphensCount();
 }
 
+const float ViewModel::GetCharacterSpacing() const
+{
+  return mModel->GetCharacterSpacing();
+}
+
+const Character* ViewModel::GetTextBuffer() const
+{
+  return mModel->GetTextBuffer();
+}
+
+const Vector<CharacterIndex>& ViewModel::GetGlyphsToCharacters() const
+{
+  return mModel->GetGlyphsToCharacters();
+}
+
 void ViewModel::ElideGlyphs()
 {
-  mIsTextElided = false;
+  mIsTextElided             = false;
+  mStartIndexOfElidedGlyphs = mFirstMiddleIndexOfElidedGlyphs = mSecondMiddleIndexOfElidedGlyphs = 0;
+  mEndIndexOfElidedGlyphs                                                                        = mModel->GetNumberOfGlyphs() - 1u;
 
-  if(mModel->IsTextElideEnabled())
+  auto                          ellipsisPosition          = GetEllipsisPosition();
+  auto                          characterSpacing          = GetCharacterSpacing();
+  const Character*              textBuffer                = GetTextBuffer();
+  const Vector<CharacterIndex>& glyphToCharacterMap       = GetGlyphsToCharacters();
+  const CharacterIndex*         glyphToCharacterMapBuffer = glyphToCharacterMap.Begin();
+  float                         calculatedAdvance         = 0.f;
+
+  if(IsTextElideEnabled())
   {
     const Length numberOfLines = mModel->GetNumberOfLines();
     if(0u != numberOfLines)
     {
       const LineRun* const lines = mModel->GetLines();
 
-      const LineRun& lastLine              = *(lines + (numberOfLines - 1u));
-      const Length   numberOfLaidOutGlyphs = lastLine.glyphRun.glyphIndex + lastLine.glyphRun.numberOfGlyphs;
+      //Get line of ellipsis
+      const LineRun* ellipsisLine     = nullptr;
+      const LineRun* ellipsisNextLine = nullptr;
+
+      for(Length lineIndex = 0; lineIndex < numberOfLines; lineIndex++)
+      {
+        const LineRun* line = (lines + lineIndex);
+        if(line->ellipsis)
+        {
+          ellipsisLine = line;
+          if(lineIndex < numberOfLines - 1u)
+          {
+            ellipsisNextLine = (lines + lineIndex + 1u);
+          }
+          break;
+        }
+      }
 
-      if(lastLine.ellipsis && (0u != numberOfLaidOutGlyphs))
+      // Check if there is a line contains Ellipsis.
+      // Then find total number of glyphs and total number of laid out glyphs.
+      // Check where to set Ellipsis glyph in line.
+      // Determine index of Ellipsis glyph and how many glyphs should be replaced by Ellipsis glyph, according to width of Ellipsis glyph.
+      if(ellipsisLine != nullptr)
       {
-        mIsTextElided                          = true;
-        TextAbstraction::FontClient fontClient = TextAbstraction::FontClient::Get();
+        // Total number of glyphs.
+        const Length numberOfGlyphs = mModel->GetNumberOfGlyphs();
+        // Total number of laid out glyphs.
+        Length numberOfActualLaidOutGlyphs = 0u;
 
-        const GlyphInfo* const glyphs    = mModel->GetGlyphs();
-        const Vector2* const   positions = mModel->GetLayout();
+        // Accumulate laid out glyphs for each line to find total number of laid out glyphs.
+        for(Length lineIndex = 0u; lineIndex < numberOfLines; lineIndex++)
+        {
+          numberOfActualLaidOutGlyphs += lines[lineIndex].glyphRun.numberOfGlyphs + lines[lineIndex].glyphRunSecondHalf.numberOfGlyphs;
+        }
 
-        // Copy the glyphs to be elided.
-        mElidedGlyphs.Resize(numberOfLaidOutGlyphs);
-        mElidedLayout.Resize(numberOfLaidOutGlyphs);
+        // Make sure there are laid out glyphs.
+        if(0u != numberOfActualLaidOutGlyphs)
+        {
+          // There are elided glyphs.
+          mIsTextElided                          = true;
+          TextAbstraction::FontClient fontClient = TextAbstraction::FontClient::Get();
 
-        GlyphInfo* elidedGlyphsBuffer    = mElidedGlyphs.Begin();
-        Vector2*   elidedPositionsBuffer = mElidedLayout.Begin();
+          // Retrieve the whole glyphs and their positions.
+          const GlyphInfo* const glyphs    = mModel->GetGlyphs();
+          const Vector2* const   positions = mModel->GetLayout();
 
-        memcpy(elidedGlyphsBuffer, glyphs, numberOfLaidOutGlyphs * sizeof(GlyphInfo));
-        memcpy(elidedPositionsBuffer, positions, numberOfLaidOutGlyphs * sizeof(Vector2));
+          // Copy the glyphs to be elided.
+          mElidedGlyphs.Resize(numberOfGlyphs);
+          mElidedLayout.Resize(numberOfGlyphs);
+          GlyphInfo* elidedGlyphsBuffer    = mElidedGlyphs.Begin();
+          Vector2*   elidedPositionsBuffer = mElidedLayout.Begin();
 
-        const Size& controlSize = mModel->GetControlSize();
+          memcpy(elidedGlyphsBuffer, glyphs, numberOfGlyphs * sizeof(GlyphInfo));
+          memcpy(elidedPositionsBuffer, positions, numberOfGlyphs * sizeof(Vector2));
 
-        if((1u == numberOfLines) &&
-           (lastLine.ascender - lastLine.descender > controlSize.height))
-        {
-          // Get the first glyph which is going to be replaced and the ellipsis glyph.
-          GlyphInfo&       glyphToRemove = *elidedGlyphsBuffer;
-          const GlyphInfo& ellipsisGlyph = fontClient.GetEllipsisGlyph(fontClient.GetPointSize(glyphToRemove.fontId));
+          const Size& controlSize = mModel->GetControlSize();
 
-          // Change the 'x' and 'y' position of the ellipsis glyph.
-          Vector2& position = *elidedPositionsBuffer;
+          // Set index where to set Ellipsis according to the selected position of Ellipsis.
+          // Start with this index to replace its glyph by Ellipsis, if the width  is not enough, then remove more glyphs.
+          GlyphIndex startIndexOfEllipsis = 0u;
+          if(ellipsisPosition == DevelText::EllipsisPosition::START)
+          {
+            // It's the fisrt glyph in line.
+            startIndexOfEllipsis = ellipsisLine->glyphRun.glyphIndex;
+          }
+          else if(ellipsisPosition == DevelText::EllipsisPosition::MIDDLE)
+          {
+            // It's the second middle of the line in case the line split to two halves.
+            // Otherwise it's It's the last glyph in line (line before all removed lines).
+            startIndexOfEllipsis = ellipsisLine->isSplitToTwoHalves ? (ellipsisLine->glyphRunSecondHalf.glyphIndex) : (ellipsisLine->glyphRun.glyphIndex + ellipsisLine->glyphRun.numberOfGlyphs - 1u);
+          }
+          else // DevelText::EllipsisPosition::END
+          {
+            // It's the last glyph in line.
+            startIndexOfEllipsis = ellipsisLine->glyphRun.glyphIndex + ellipsisLine->glyphRun.numberOfGlyphs - 1u;
+          }
 
-          position.x = ellipsisGlyph.xBearing;
-          position.y = -lastLine.ascender + controlSize.height - ellipsisGlyph.yBearing;
+          // When the hight is not enough then show one glyph and that should be the first laid out glyph.
+          if((1u == numberOfLines) &&
+             (ellipsisLine->ascender - ellipsisLine->descender > controlSize.height))
+          {
+            // Replace the first glyph with ellipsis glyph
+            auto indexOfFirstGlyph = (ellipsisPosition == DevelText::EllipsisPosition::START) ? startIndexOfEllipsis : 0u;
 
-          // Replace the glyph by the ellipsis glyph and resize the buffers.
-          glyphToRemove = ellipsisGlyph;
+            // Regardless where the location of ellipsis,in-case the hight of line is greater than control's height
+            // then replace the first glyph with ellipsis glyph.
 
-          mElidedGlyphs.Resize(1u);
-          mElidedLayout.Resize(1u);
+            // Get the first glyph which is going to be replaced and the ellipsis glyph.
+            GlyphInfo&       glyphToRemove = *(elidedGlyphsBuffer + indexOfFirstGlyph);
+            const GlyphInfo& ellipsisGlyph = fontClient.GetEllipsisGlyph(fontClient.GetPointSize(glyphToRemove.fontId));
 
-          return;
-        }
+            // Change the 'x' and 'y' position of the ellipsis glyph.
+            Vector2& position = *(elidedPositionsBuffer + indexOfFirstGlyph);
 
-        // firstPenX, penY and firstPenSet are used to position the ellipsis glyph if needed.
-        float firstPenX   = 0.f; // Used if rtl text is elided.
-        float penY        = 0.f;
-        bool  firstPenSet = false;
+            position.x = ellipsisGlyph.xBearing;
+            position.y = -ellipsisLine->ascender + controlSize.height - ellipsisGlyph.yBearing;
 
-        // Add the ellipsis glyph.
-        bool       inserted              = false;
-        float      removedGlypsWidth     = 0.f;
-        Length     numberOfRemovedGlyphs = 0u;
-        GlyphIndex index                 = numberOfLaidOutGlyphs - 1u;
+            // Replace the glyph by the ellipsis glyph and resize the buffers.
+            glyphToRemove = ellipsisGlyph;
 
-        // The ellipsis glyph has to fit in the place where the last glyph(s) is(are) removed.
-        while(!inserted)
-        {
-          const GlyphInfo& glyphToRemove = *(elidedGlyphsBuffer + index);
+            mElidedGlyphs.Resize(1u);
+            mElidedLayout.Resize(1u);
 
-          if(0u != glyphToRemove.fontId)
-          {
-            // i.e. The font id of the glyph shaped from the '\n' character is zero.
+            mEndIndexOfElidedGlyphs = mStartIndexOfElidedGlyphs = mFirstMiddleIndexOfElidedGlyphs = mSecondMiddleIndexOfElidedGlyphs = indexOfFirstGlyph;
 
-            // Need to reshape the glyph as the font may be different in size.
-            const GlyphInfo& ellipsisGlyph = fontClient.GetEllipsisGlyph(fontClient.GetPointSize(glyphToRemove.fontId));
+            return;
+          }
 
-            if(!firstPenSet || EqualsZero(glyphToRemove.advance))
+          // firstPenX, penY and firstPenSet are used to position the ellipsis glyph if needed.
+          float firstPenX   = 0.f; // Used if rtl text is elided.
+          float penY        = 0.f;
+          bool  firstPenSet = false;
+
+          // Add the ellipsis glyph.
+          bool       inserted              = false;
+          float      removedGlypsWidth     = 0.f;
+          Length     numberOfRemovedGlyphs = 0u;
+          GlyphIndex indexOfEllipsis       = startIndexOfEllipsis;
+
+          // Tail Mode: start by the end of line.
+          bool isTailMode = (ellipsisPosition == DevelText::EllipsisPosition::END) ||
+                            (ellipsisPosition == DevelText::EllipsisPosition::MIDDLE && numberOfLines != 1u);
+
+          // The ellipsis glyph has to fit in the place where the last glyph(s) is(are) removed.
+          while(!inserted)
+          {
+            const GlyphInfo& glyphToRemove = *(elidedGlyphsBuffer + indexOfEllipsis);
+
+            if(0u != glyphToRemove.fontId)
             {
-              const Vector2& position = *(elidedPositionsBuffer + index);
+              // i.e. The font id of the glyph shaped from the '\n' character is zero.
 
-              // Calculates the penY of the current line. It will be used to position the ellipsis glyph.
-              penY = position.y + glyphToRemove.yBearing;
+              // Need to reshape the glyph as the font may be different in size.
+              const GlyphInfo& ellipsisGlyph = fontClient.GetEllipsisGlyph(fontClient.GetPointSize(glyphToRemove.fontId));
 
-              // Calculates the first penX which will be used if rtl text is elided.
-              firstPenX = position.x - glyphToRemove.xBearing;
-              if(firstPenX < -ellipsisGlyph.xBearing)
+              if(!firstPenSet || EqualsZero(glyphToRemove.advance))
               {
-                // Avoids to exceed the bounding box when rtl text is elided.
-                firstPenX = -ellipsisGlyph.xBearing;
-              }
+                const Vector2& position = *(elidedPositionsBuffer + indexOfEllipsis);
 
-              removedGlypsWidth = -ellipsisGlyph.xBearing;
+                // Calculates the penY of the current line. It will be used to position the ellipsis glyph.
+                penY = position.y + glyphToRemove.yBearing;
+
+                // Calculates the first penX which will be used if rtl text is elided.
+                firstPenX = position.x - glyphToRemove.xBearing;
+                if(firstPenX < -ellipsisGlyph.xBearing)
+                {
+                  // Avoids to exceed the bounding box when rtl text is elided.
+                  firstPenX = -ellipsisGlyph.xBearing;
+                }
+
+                removedGlypsWidth = -ellipsisGlyph.xBearing;
 
-              if(!EqualsZero(firstPenX))
-              {
                 firstPenSet = true;
               }
-            }
 
-            removedGlypsWidth += std::min(glyphToRemove.advance, (glyphToRemove.xBearing + glyphToRemove.width));
+              calculatedAdvance = GetCalculatedAdvance(*(textBuffer + (*(glyphToCharacterMapBuffer + indexOfEllipsis))), characterSpacing, glyphToRemove.advance);
+              removedGlypsWidth += std::min(calculatedAdvance, (glyphToRemove.xBearing + glyphToRemove.width));
 
-            // Calculate the width of the ellipsis glyph and check if it fits.
-            const float ellipsisGlyphWidth = ellipsisGlyph.width + ellipsisGlyph.xBearing;
+              // Calculate the width of the ellipsis glyph and check if it fits.
+              const float ellipsisGlyphWidth = ellipsisGlyph.width + ellipsisGlyph.xBearing;
 
-            // If it is the last glyph to remove, add the ellipsis glyph without checking its width.
-            if((ellipsisGlyphWidth < removedGlypsWidth) || (index == 0u))
-            {
-              GlyphInfo& glyphInfo = *(elidedGlyphsBuffer + index);
-              Vector2&   position  = *(elidedPositionsBuffer + index);
-              position.x -= (0.f > glyphInfo.xBearing) ? glyphInfo.xBearing : 0.f;
+              // If it is the last glyph to remove, add the ellipsis glyph without checking its width.
+              if((ellipsisGlyphWidth < removedGlypsWidth) || (isTailMode ? (indexOfEllipsis == 0u) : (indexOfEllipsis == numberOfGlyphs - 1u)))
+              {
+                GlyphInfo& glyphInfo = *(elidedGlyphsBuffer + indexOfEllipsis);
+                Vector2&   position  = *(elidedPositionsBuffer + indexOfEllipsis);
+                position.x -= (0.f > glyphInfo.xBearing) ? glyphInfo.xBearing : 0.f;
 
-              // Replace the glyph by the ellipsis glyph.
-              glyphInfo = ellipsisGlyph;
+                // Replace the glyph by the ellipsis glyph.
+                glyphInfo = ellipsisGlyph;
 
-              // Change the 'x' and 'y' position of the ellipsis glyph.
-              if(position.x > firstPenX)
-              {
-                position.x = firstPenX;
-                if(ellipsisGlyphWidth < removedGlypsWidth)
+                // Change the 'x' and 'y' position of the ellipsis glyph.
+                if(position.x > firstPenX)
                 {
-                  position.x += removedGlypsWidth - ellipsisGlyphWidth;
+                  if(isTailMode)
+                  {
+                    // To handle case of the mixed languages (LTR then RTL) with
+                    // EllipsisPosition::END and the LayoutDirection::RIGHT_TO_LEFT
+                    float nextXPositions = ellipsisLine->width;
+                    if(indexOfEllipsis + 1u < numberOfGlyphs)
+                    {
+                      Vector2& positionOfNextGlyph = *(elidedPositionsBuffer + indexOfEllipsis + 1u);
+                      nextXPositions               = positionOfNextGlyph.x;
+                    }
+
+                    if(position.x > nextXPositions) // RTL language
+                    {
+                      if((indexOfEllipsis > 0u) && ((position.x - nextXPositions) > removedGlypsWidth))
+                      {
+                        // To handle mixed directions
+                        // Re-calculates the first penX which will be used if rtl text is elided.
+                        firstPenX = position.x - glyphToRemove.xBearing;
+                        if(firstPenX < -ellipsisGlyph.xBearing)
+                        {
+                          // Avoids to exceed the bounding box when rtl text is elided.
+                          firstPenX = -ellipsisGlyph.xBearing;
+                        }
+                        //Reset the width of removed glyphs
+                        removedGlypsWidth = std::min(calculatedAdvance, (glyphToRemove.xBearing + glyphToRemove.width)) - ellipsisGlyph.xBearing;
+
+                        --indexOfEllipsis;
+                        continue;
+                      }
+                      else
+                      {
+                        // To handle the case of RTL language with EllipsisPosition::END
+                        position.x = firstPenX + removedGlypsWidth - ellipsisGlyphWidth;
+                      }
+                    }
+                  }
+                  else
+                  {
+                    // To handle the case of LTR language with EllipsisPosition::START
+                    position.x = firstPenX + removedGlypsWidth - ellipsisGlyphWidth;
+                  }
+                }
+                else
+                {
+                  if(!isTailMode)
+                  {
+                    // To handle case of the mixed languages (RTL then LTR) with
+                    // EllipsisPosition::START and the LayoutDirection::RIGHT_TO_LEFT
+                    float nextXPositions = ellipsisLine->width;
+                    if(indexOfEllipsis + 1u < numberOfGlyphs)
+                    {
+                      Vector2& positionOfNextGlyph = *(elidedPositionsBuffer + indexOfEllipsis + 1u);
+                      nextXPositions               = positionOfNextGlyph.x;
+                    }
+
+                    if(position.x < nextXPositions) // LTR language
+                    {
+                      position.x = firstPenX + removedGlypsWidth - ellipsisGlyphWidth;
+
+                      if((position.x + ellipsisGlyphWidth + ellipsisGlyph.xBearing) > nextXPositions)
+                      {
+                        position.x -= (position.x + ellipsisGlyphWidth + ellipsisGlyph.xBearing) - nextXPositions;
+                      }
+                    }
+                  }
                 }
-              }
 
-              position.x += ellipsisGlyph.xBearing;
-              position.y = penY - ellipsisGlyph.yBearing;
+                position.x += ellipsisGlyph.xBearing;
+                position.y = penY - ellipsisGlyph.yBearing;
 
-              inserted = true;
+                inserted = true;
+              }
             }
-          }
 
-          if(!inserted)
+            if(!inserted)
+            {
+              if(!isTailMode && indexOfEllipsis < numberOfGlyphs - 1u)
+              {
+                // Tail Mode: remove glyphs from startIndexOfEllipsis then decrement indexOfEllipsis, until arrive to index zero.
+                ++indexOfEllipsis;
+              }
+              else if(isTailMode && indexOfEllipsis > 0u)
+              {
+                // Not Tail Mode: remove glyphs from startIndexOfEllipsis then increase indexOfEllipsis, until arrive to last index (numberOfGlyphs - 1u).
+                --indexOfEllipsis;
+              }
+              else
+              {
+                // No space for the ellipsis.
+                inserted = true;
+              }
+              ++numberOfRemovedGlyphs;
+            }
+          } // while( !inserted )
+
+          //Reduce size, shift glyphs and start from ellipsis glyph
+          Length numberOfElidedGlyphs = numberOfActualLaidOutGlyphs - numberOfRemovedGlyphs;
+          mElidedGlyphs.Resize(numberOfElidedGlyphs);
+          mElidedLayout.Resize(numberOfElidedGlyphs);
+
+          if(ellipsisPosition == DevelText::EllipsisPosition::START)
           {
-            if(index > 0u)
+            // 'Shifts' glyphs after ellipsis glyph and 'Removes' before ellipsis glyph
+            memcpy(elidedGlyphsBuffer, elidedGlyphsBuffer + indexOfEllipsis, numberOfElidedGlyphs * sizeof(GlyphInfo));
+            memcpy(elidedPositionsBuffer, elidedPositionsBuffer + indexOfEllipsis, numberOfElidedGlyphs * sizeof(Vector2));
+
+            mStartIndexOfElidedGlyphs = mFirstMiddleIndexOfElidedGlyphs = mSecondMiddleIndexOfElidedGlyphs = indexOfEllipsis;
+          }
+          else if(ellipsisPosition == DevelText::EllipsisPosition::MIDDLE)
+          {
+            // 'Shifts and connects' glyphs before and after ellipsis glyph and 'Removes' in-between.
+            bool isOnlySecondHalf = false;
+
+            if(isTailMode)
             {
-              --index;
+              mFirstMiddleIndexOfElidedGlyphs = indexOfEllipsis;
+              if(ellipsisNextLine != nullptr)
+              {
+                mSecondMiddleIndexOfElidedGlyphs = ellipsisNextLine->glyphRun.glyphIndex;
+              }
+              else
+              {
+                mEndIndexOfElidedGlyphs = mSecondMiddleIndexOfElidedGlyphs = mFirstMiddleIndexOfElidedGlyphs;
+              }
             }
             else
             {
-              // No space for the ellipsis.
-              inserted = true;
+              mFirstMiddleIndexOfElidedGlyphs  = (ellipsisLine->glyphRun.numberOfGlyphs > 0u) ? (ellipsisLine->glyphRun.glyphIndex + ellipsisLine->glyphRun.numberOfGlyphs - 1u) : (ellipsisLine->glyphRun.glyphIndex);
+              mSecondMiddleIndexOfElidedGlyphs = indexOfEllipsis;
+              isOnlySecondHalf                 = ellipsisLine->glyphRun.numberOfGlyphs == 0u && ellipsisLine->glyphRunSecondHalf.numberOfGlyphs > 0u;
             }
-            ++numberOfRemovedGlyphs;
-          }
-        } // while( !inserted )
 
-        // 'Removes' all the glyphs after the ellipsis glyph.
-        const Length numberOfGlyphs = numberOfLaidOutGlyphs - numberOfRemovedGlyphs;
-        mElidedGlyphs.Resize(numberOfGlyphs);
-        mElidedLayout.Resize(numberOfGlyphs);
+            if(isOnlySecondHalf)
+            {
+              Length numberOfSecondHalfGlyphs = numberOfElidedGlyphs - mFirstMiddleIndexOfElidedGlyphs;
+
+              //Copy elided glyphs after the ellipsis glyph.
+              memcpy(elidedGlyphsBuffer + mFirstMiddleIndexOfElidedGlyphs, elidedGlyphsBuffer + mSecondMiddleIndexOfElidedGlyphs, numberOfSecondHalfGlyphs * sizeof(GlyphInfo));
+              memcpy(elidedPositionsBuffer + mFirstMiddleIndexOfElidedGlyphs, elidedPositionsBuffer + mSecondMiddleIndexOfElidedGlyphs, numberOfSecondHalfGlyphs * sizeof(Vector2));
+            }
+            else
+            {
+              Length numberOfSecondHalfGlyphs = numberOfElidedGlyphs - mFirstMiddleIndexOfElidedGlyphs + 1u;
+
+              //Copy elided glyphs after the ellipsis glyph.
+              memcpy(elidedGlyphsBuffer + mFirstMiddleIndexOfElidedGlyphs + 1u, elidedGlyphsBuffer + mSecondMiddleIndexOfElidedGlyphs, numberOfSecondHalfGlyphs * sizeof(GlyphInfo));
+              memcpy(elidedPositionsBuffer + mFirstMiddleIndexOfElidedGlyphs + 1u, elidedPositionsBuffer + mSecondMiddleIndexOfElidedGlyphs, numberOfSecondHalfGlyphs * sizeof(Vector2));
+            }
+          }
+          else // DevelText::EllipsisPosition::END
+          {
+            // 'Removes' all the glyphs after the ellipsis glyph.
+            mEndIndexOfElidedGlyphs = indexOfEllipsis;
+          }
+        }
       }
     }
   }
 }
 
+float ViewModel::GetStrikethroughHeight() const
+{
+  return mModel->GetStrikethroughHeight();
+}
+
+const Vector4& ViewModel::GetStrikethroughColor() const
+{
+  return mModel->GetStrikethroughColor();
+}
+
+bool ViewModel::IsStrikethroughEnabled() const
+{
+  return mModel->IsStrikethroughEnabled();
+}
+
+Length ViewModel::GetNumberOfStrikethroughRuns() const
+{
+  return mModel->GetNumberOfStrikethroughRuns();
+}
+
+void ViewModel::GetStrikethroughRuns(StrikethroughGlyphRun* strikethroughRuns, StrikethroughRunIndex index, Length numberOfRuns) const
+{
+  mModel->GetStrikethroughRuns(strikethroughRuns, index, numberOfRuns);
+}
+
+Length ViewModel::GetNumberOfBoundedParagraphRuns() const
+{
+  return mModel->GetNumberOfBoundedParagraphRuns();
+}
+
+const Vector<BoundedParagraphRun>& ViewModel::GetBoundedParagraphRuns() const
+{
+  return mModel->GetBoundedParagraphRuns();
+}
+
+Length ViewModel::GetNumberOfCharacterSpacingGlyphRuns() const
+{
+  return mModel->GetNumberOfCharacterSpacingGlyphRuns();
+}
+
+const Vector<CharacterSpacingGlyphRun>& ViewModel::GetCharacterSpacingGlyphRuns() const
+{
+  return mModel->GetCharacterSpacingGlyphRuns();
+}
+
 } // namespace Text
 
 } // namespace Toolkit