fixed ellipsis position with multi line
[platform/core/uifw/dali-toolkit.git] / dali-toolkit / internal / text / rendering / view-model.cpp
index f695cd2..2187487 100644 (file)
@@ -35,7 +35,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 +77,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 +121,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 +209,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();
@@ -247,157 +301,283 @@ Length ViewModel::GetHyphensCount() const
 
 void ViewModel::ElideGlyphs()
 {
-  mIsTextElided = false;
+  mIsTextElided             = false;
+  mStartIndexOfElidedGlyphs = mFirstMiddleIndexOfElidedGlyphs = mSecondMiddleIndexOfElidedGlyphs = 0;
+  mEndIndexOfElidedGlyphs                                                                        = mModel->GetNumberOfGlyphs() - 1u;
 
-  if(mModel->IsTextElideEnabled())
+  auto ellipsisPosition = GetEllipsisPosition();
+
+  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;
 
-      if(lastLine.ellipsis && (0u != numberOfLaidOutGlyphs))
+      for(Length lineIndex = 0; lineIndex < numberOfLines; lineIndex++)
       {
-        mIsTextElided                          = true;
-        TextAbstraction::FontClient fontClient = TextAbstraction::FontClient::Get();
+        const LineRun* line = (lines + lineIndex);
+        if(line->ellipsis)
+        {
+          ellipsisLine = line;
+          if(lineIndex < numberOfLines - 1u)
+          {
+            ellipsisNextLine = (lines + lineIndex + 1u);
+          }
+          break;
+        }
+      }
 
-        const GlyphInfo* const glyphs    = mModel->GetGlyphs();
-        const Vector2* const   positions = mModel->GetLayout();
+      // 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)
+      {
+        // Total number of glyphs.
+        const Length numberOfGlyphs = mModel->GetNumberOfGlyphs();
+        // Total number of laid out glyphs.
+        Length numberOfActualLaidOutGlyphs = 0u;
 
-        // Copy the glyphs to be elided.
-        mElidedGlyphs.Resize(numberOfLaidOutGlyphs);
-        mElidedLayout.Resize(numberOfLaidOutGlyphs);
+        // 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;
+        }
 
-        GlyphInfo* elidedGlyphsBuffer    = mElidedGlyphs.Begin();
-        Vector2*   elidedPositionsBuffer = mElidedLayout.Begin();
+        // Make sure there are laid out glyphs.
+        if(0u != numberOfActualLaidOutGlyphs)
+        {
+          // There are elided glyphs.
+          mIsTextElided                          = true;
+          TextAbstraction::FontClient fontClient = TextAbstraction::FontClient::Get();
 
-        memcpy(elidedGlyphsBuffer, glyphs, numberOfLaidOutGlyphs * sizeof(GlyphInfo));
-        memcpy(elidedPositionsBuffer, positions, numberOfLaidOutGlyphs * sizeof(Vector2));
+          // Retrieve the whole glyphs and their positions.
+          const GlyphInfo* const glyphs    = mModel->GetGlyphs();
+          const Vector2* const   positions = mModel->GetLayout();
 
-        const Size& controlSize = mModel->GetControlSize();
+          // Copy the glyphs to be elided.
+          mElidedGlyphs.Resize(numberOfGlyphs);
+          mElidedLayout.Resize(numberOfGlyphs);
+          GlyphInfo* elidedGlyphsBuffer    = mElidedGlyphs.Begin();
+          Vector2*   elidedPositionsBuffer = mElidedLayout.Begin();
 
-        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));
+          memcpy(elidedGlyphsBuffer, glyphs, numberOfGlyphs * sizeof(GlyphInfo));
+          memcpy(elidedPositionsBuffer, positions, numberOfGlyphs * sizeof(Vector2));
 
-          // Change the 'x' and 'y' position of the ellipsis glyph.
-          Vector2& position = *elidedPositionsBuffer;
+          const Size& controlSize = mModel->GetControlSize();
 
-          position.x = ellipsisGlyph.xBearing;
-          position.y = -lastLine.ascender + controlSize.height - ellipsisGlyph.yBearing;
+          // 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;
+          }
 
-          // Replace the glyph by the ellipsis glyph and resize the buffers.
-          glyphToRemove = ellipsisGlyph;
+          // 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;
 
-          mElidedGlyphs.Resize(1u);
-          mElidedLayout.Resize(1u);
+            // 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.
 
-          return;
-        }
+            // 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));
 
-        // 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;
+            // Change the 'x' and 'y' position of the ellipsis glyph.
+            Vector2& position = *(elidedPositionsBuffer + indexOfFirstGlyph);
 
-        // Add the ellipsis glyph.
-        bool       inserted              = false;
-        float      removedGlypsWidth     = 0.f;
-        Length     numberOfRemovedGlyphs = 0u;
-        GlyphIndex index                 = numberOfLaidOutGlyphs - 1u;
+            position.x = ellipsisGlyph.xBearing;
+            position.y = -ellipsisLine->ascender + controlSize.height - ellipsisGlyph.yBearing;
 
-        // The ellipsis glyph has to fit in the place where the last glyph(s) is(are) removed.
-        while(!inserted)
-        {
-          const GlyphInfo& glyphToRemove = *(elidedGlyphsBuffer + index);
+            // Replace the glyph by the ellipsis glyph and resize the buffers.
+            glyphToRemove = ellipsisGlyph;
 
-          if(0u != glyphToRemove.fontId)
-          {
-            // i.e. The font id of the glyph shaped from the '\n' character is zero.
+            mElidedGlyphs.Resize(1u);
+            mElidedLayout.Resize(1u);
 
-            // Need to reshape the glyph as the font may be different in size.
-            const GlyphInfo& ellipsisGlyph = fontClient.GetEllipsisGlyph(fontClient.GetPointSize(glyphToRemove.fontId));
+            mEndIndexOfElidedGlyphs = mStartIndexOfElidedGlyphs = mFirstMiddleIndexOfElidedGlyphs = mSecondMiddleIndexOfElidedGlyphs = indexOfFirstGlyph;
+
+            return;
+          }
+
+          // 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;
 
-            if(!firstPenSet || EqualsZero(glyphToRemove.advance))
+          // 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));
+              removedGlypsWidth += std::min(glyphToRemove.advance, (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;
+                  position.x = firstPenX + removedGlypsWidth - ellipsisGlyphWidth;
                 }
-              }
 
-              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)
+            {
+              mFirstMiddleIndexOfElidedGlyphs = indexOfEllipsis;
+              if(ellipsisNextLine != nullptr)
+              {
+                mSecondMiddleIndexOfElidedGlyphs = ellipsisNextLine->glyphRun.glyphIndex;
+              }
+              else
+              {
+                mEndIndexOfElidedGlyphs = mSecondMiddleIndexOfElidedGlyphs = mFirstMiddleIndexOfElidedGlyphs;
+              }
+            }
+            else
             {
-              --index;
+              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;
+            }
+
+            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
             {
-              // No space for the ellipsis.
-              inserted = true;
+              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));
             }
-            ++numberOfRemovedGlyphs;
           }
-        } // while( !inserted )
-
-        // 'Removes' all the glyphs after the ellipsis glyph.
-        const Length numberOfGlyphs = numberOfLaidOutGlyphs - numberOfRemovedGlyphs;
-        mElidedGlyphs.Resize(numberOfGlyphs);
-        mElidedLayout.Resize(numberOfGlyphs);
+          else // DevelText::EllipsisPosition::END
+          {
+            // 'Removes' all the glyphs after the ellipsis glyph.
+            mEndIndexOfElidedGlyphs = indexOfEllipsis;
+          }
+        }
       }
     }
   }