Add support for text geometry
[platform/core/uifw/dali-toolkit.git] / dali-toolkit / internal / text / text-geometry.cpp
1 /*
2  * Copyright (c) 2021 Samsung Electronics Co., Ltd.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  *
16  */
17
18 // CLASS HEADER
19 #include <dali-toolkit/internal/text/text-geometry.h>
20
21 // EXTERNAL INCLUDES
22 #include <dali/integration-api/debug.h>
23
24 // INTERNAL INCLUDES
25 #include <dali-toolkit/internal/text/cursor-helper-functions.h>
26
27 using namespace Dali;
28
29 namespace Dali
30 {
31 namespace Toolkit
32 {
33 namespace Text
34 {
35 bool GetNextLine(GlyphIndex index, LineIndex& lineIndex, LineRun*& lineRun, GlyphIndex& lastGlyphOfLine, Length numberOfLines)
36 {
37   if(index == lastGlyphOfLine)
38   {
39     ++lineIndex;
40     if(lineIndex < numberOfLines)
41     {
42       ++lineRun;
43       return true;
44     }
45   }
46
47   return false;
48 }
49
50 void UpdateLineInfo(const LineRun* lineRun, float& currentLineOffset, float& currentLineHeight, GlyphIndex& lastGlyphOfLine)
51 {
52   lastGlyphOfLine   = lineRun->glyphRun.glyphIndex + lineRun->glyphRun.numberOfGlyphs - 1u;
53   currentLineOffset = currentLineOffset + currentLineHeight;
54   currentLineHeight = GetLineHeight(*lineRun);
55 }
56
57 void GetTextGeometry(ModelPtr textModel, CharacterIndex startIndex, CharacterIndex endIndex, Vector<Vector2>& sizesList, Vector<Vector2>& positionsList)
58 {
59   VisualModelPtr&  visualModel  = textModel->mVisualModel;
60   LogicalModelPtr& logicalModel = textModel->mLogicalModel;
61
62   const GlyphIndex* const         charactersToGlyphBuffer        = visualModel->mCharactersToGlyph.Begin();
63   const Length* const             glyphsPerCharacterBuffer       = visualModel->mGlyphsPerCharacter.Begin();
64   const GlyphInfo* const          glyphsBuffer                   = visualModel->mGlyphs.Begin();
65   const Vector2* const            positionsBuffer                = visualModel->mGlyphPositions.Begin();
66   const Length* const             charactersPerGlyphBuffer       = visualModel->mCharactersPerGlyph.Begin();
67   const CharacterIndex* const     glyphToCharacterBuffer         = visualModel->mGlyphsToCharacters.Begin();
68   const CharacterDirection* const modelCharacterDirectionsBuffer = (0u != logicalModel->mCharacterDirections.Count()) ? logicalModel->mCharacterDirections.Begin() : NULL;
69
70   if((startIndex < 0 && endIndex < 0) || (startIndex >= logicalModel->mText.Count() && endIndex >= logicalModel->mText.Count()))
71     return;
72
73   if(startIndex < 0)
74     startIndex = 0;
75
76   if(endIndex < 0)
77     endIndex = 0;
78
79   if(startIndex >= logicalModel->mText.Count())
80     startIndex = logicalModel->mText.Count() - 1;
81
82   if(endIndex >= logicalModel->mText.Count())
83     endIndex = logicalModel->mText.Count() - 1;
84
85   if(startIndex > endIndex)
86   {
87     std::swap(startIndex, endIndex);
88   }
89
90   LineRun*   lineRun    = visualModel->mLines.Begin();
91   GlyphIndex glyphStart = *(charactersToGlyphBuffer + startIndex);
92
93   //if glyph not in the first line (in some ellipsis cases)
94   if(glyphStart < lineRun->glyphRun.glyphIndex)
95   {
96     glyphStart = lineRun->glyphRun.glyphIndex;
97     startIndex = *(glyphToCharacterBuffer + glyphStart);
98
99     if(startIndex > endIndex)
100     {
101       std::swap(startIndex, endIndex);
102     }
103   }
104
105   const Length numberOfGlyphs = *(glyphsPerCharacterBuffer + endIndex);
106   GlyphIndex   glyphEnd       = *(charactersToGlyphBuffer + endIndex) + ((numberOfGlyphs > 0) ? numberOfGlyphs - 1u : 0u);
107   LineIndex    lineIndex      = visualModel->GetLineOfCharacter(startIndex);
108   Length       numberOfLines  = visualModel->GetTotalNumberOfLines();
109
110   LineIndex firstLineIndex = lineIndex;
111   Size      textInLineSize;
112   Vector2   textInLinePosition;
113
114   lineRun += firstLineIndex;
115
116   //get the first line and its vertical offset
117   float      currentLineOffset = CalculateLineOffset(visualModel->mLines, firstLineIndex);
118   float      currentLineHeight = GetLineHeight(*lineRun);
119   GlyphIndex lastGlyphOfLine   = lineRun->glyphRun.glyphIndex + lineRun->glyphRun.numberOfGlyphs - 1;
120
121   // Check if the first/last glyph is a ligature that needs be splitted like English fi or Arabic ﻻ.
122   const Length numberOfCharactersStart = *(charactersPerGlyphBuffer + glyphStart);
123   const Length numberOfCharactersEnd   = *(charactersPerGlyphBuffer + glyphEnd);
124
125   bool splitStartGlyph = (numberOfCharactersStart > 1u) && HasLigatureMustBreak(logicalModel->GetScript(startIndex));
126   bool splitEndGlyph   = (glyphStart != glyphEnd) && (numberOfCharactersEnd > 1u) && HasLigatureMustBreak(logicalModel->GetScript(endIndex));
127
128   Vector2            currentSize;
129   Vector2            currentPosition;
130   Vector2            blockSize;
131   Vector2            blockPos;
132   CharacterDirection isCurrentRightToLeft;
133
134   CharacterDirection                      isPrevoiusRightToLeft           = (nullptr != modelCharacterDirectionsBuffer ? *(modelCharacterDirectionsBuffer + startIndex) : false);
135   const bool                              isEllipsisEnabled               = textModel->mElideEnabled;
136   const GlyphIndex                        startIndexOfGlyphs              = textModel->GetStartIndexOfElidedGlyphs();
137   const GlyphIndex                        endIndexOfGlyphs                = textModel->GetEndIndexOfElidedGlyphs();
138   const GlyphIndex                        firstMiddleIndexOfElidedGlyphs  = textModel->GetFirstMiddleIndexOfElidedGlyphs();
139   const GlyphIndex                        secondMiddleIndexOfElidedGlyphs = textModel->GetSecondMiddleIndexOfElidedGlyphs();
140   const DevelText::EllipsisPosition::Type ellipsisPosition                = textModel->GetEllipsisPosition();
141
142   for(GlyphIndex index = glyphStart; index <= glyphEnd; ++index)
143   {
144     if(isEllipsisEnabled)
145     {
146       if(ellipsisPosition == DevelText::EllipsisPosition::MIDDLE)
147       {
148         if(index >= firstMiddleIndexOfElidedGlyphs &&
149            index < secondMiddleIndexOfElidedGlyphs)
150         {
151           if((index - 1 == firstMiddleIndexOfElidedGlyphs) && (firstMiddleIndexOfElidedGlyphs != 0))
152           {
153             sizesList.PushBack(blockSize);
154             positionsList.PushBack(blockPos);
155           }
156
157           if(GetNextLine(index, lineIndex, lineRun, lastGlyphOfLine, numberOfLines))
158           {
159             UpdateLineInfo(lineRun, currentLineOffset, currentLineHeight, lastGlyphOfLine);
160           }
161           // Ignore any glyph that was removed
162           continue;
163         }
164       }
165       else
166       {
167         if((ellipsisPosition == DevelText::EllipsisPosition::END) && (index >= endIndexOfGlyphs))
168         {
169           //skip remaining elided glyphs
170           break;
171         }
172         else if((ellipsisPosition == DevelText::EllipsisPosition::START) && (index <= startIndexOfGlyphs))
173         {
174           if(GetNextLine(index, lineIndex, lineRun, lastGlyphOfLine, numberOfLines))
175           {
176             UpdateLineInfo(lineRun, currentLineOffset, currentLineHeight, lastGlyphOfLine);
177           }
178
179           continue;
180         }
181       }
182     }
183
184     const GlyphInfo& glyph    = *(glyphsBuffer + index);
185     const Vector2&   position = *(positionsBuffer + index);
186
187     // If NULL, means all of the characters is left to right.
188     isCurrentRightToLeft = (nullptr != modelCharacterDirectionsBuffer ? *(modelCharacterDirectionsBuffer + *(glyphToCharacterBuffer + index)) : false);
189
190     if(splitStartGlyph && (index == glyphStart))
191     {
192       // If the first glyph is a ligature that needs to be splitted, we may need only to add part of the glyph.
193       const float          glyphAdvance       = glyph.advance / static_cast<float>(numberOfCharactersStart);
194       const CharacterIndex interGlyphIndex    = startIndex - *(glyphToCharacterBuffer + glyphStart);
195       const Length         numberOfCharacters = (glyphStart == glyphEnd) ? (endIndex - startIndex) + 1 : (numberOfCharactersStart - interGlyphIndex);
196
197       currentPosition.x = lineRun->alignmentOffset + position.x - glyph.xBearing + textModel->mScrollPosition.x + glyphAdvance * static_cast<float>(isCurrentRightToLeft ? (numberOfCharactersStart - interGlyphIndex - numberOfCharacters) : interGlyphIndex);
198       currentPosition.y = currentLineOffset + textModel->mScrollPosition.y;
199       currentSize.x     = static_cast<float>(numberOfCharacters) * glyphAdvance;
200       currentSize.y     = currentLineHeight;
201       splitStartGlyph   = false;
202     }
203     else if(splitEndGlyph && (index == glyphEnd))
204     {
205       const float          glyphAdvance       = glyph.advance / static_cast<float>(numberOfCharactersEnd);
206       const CharacterIndex interGlyphIndex    = endIndex - *(glyphToCharacterBuffer + glyphEnd);
207       const Length         numberOfCharacters = numberOfCharactersEnd - interGlyphIndex - 1;
208
209       currentPosition.x = lineRun->alignmentOffset + position.x - glyph.xBearing + textModel->mScrollPosition.x + (isCurrentRightToLeft ? (glyphAdvance * static_cast<float>(numberOfCharacters)) : 0.f);
210       currentPosition.y = currentLineOffset + textModel->mScrollPosition.y;
211       currentSize.x     = static_cast<float>(interGlyphIndex + 1) * glyphAdvance;
212       currentSize.y     = currentLineHeight;
213       splitEndGlyph     = false;
214     }
215     else
216     {
217       currentPosition.x = lineRun->alignmentOffset + position.x - glyph.xBearing + textModel->mScrollPosition.x;
218       currentPosition.y = currentLineOffset + textModel->mScrollPosition.y;
219       currentSize.x     = glyph.advance;
220       currentSize.y     = currentLineHeight;
221
222       // if there is next line to retrieve.
223       if(GetNextLine(index, lineIndex, lineRun, lastGlyphOfLine, numberOfLines))
224       {
225         UpdateLineInfo(lineRun, currentLineOffset, currentLineHeight, lastGlyphOfLine);
226       }
227     }
228
229     if((index == glyphStart) || (isEllipsisEnabled && (((ellipsisPosition == DevelText::EllipsisPosition::MIDDLE) && (index == secondMiddleIndexOfElidedGlyphs)) || ((ellipsisPosition == DevelText::EllipsisPosition::START) && (index - 1 == startIndexOfGlyphs)))))
230     {
231       blockPos  = currentPosition;
232       blockSize = currentSize;
233     }
234     else if((isPrevoiusRightToLeft != isCurrentRightToLeft) || (blockPos.y != currentPosition.y)) //new direction or new line
235     {
236       sizesList.PushBack(blockSize);
237       positionsList.PushBack(blockPos);
238
239       blockPos  = currentPosition;
240       blockSize = currentSize;
241     }
242     else
243     {
244       if(isCurrentRightToLeft)
245       {
246         blockPos.x -= currentSize.x;
247       }
248
249       blockSize.x += currentSize.x;
250     }
251
252     isPrevoiusRightToLeft = isCurrentRightToLeft;
253   }
254
255   //add last block
256   sizesList.PushBack(blockSize);
257   positionsList.PushBack(blockPos);
258 }
259
260 } // namespace Text
261
262 } // namespace Toolkit
263
264 } // namespace Dali