09d2c92a71189b0fa53f40ca6c758a282971b05f
[platform/core/uifw/dali-toolkit.git] / dali-toolkit / internal / text / text-geometry.cpp
1 /*
2  * Copyright (c) 2022 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 #include <dali-toolkit/internal/text/line-run.h>
27 #include <dali-toolkit/internal/text/visual-model-impl.h>
28
29 using namespace Dali;
30
31 namespace Dali
32 {
33 namespace Toolkit
34 {
35 namespace Text
36 {
37 bool GetNextLine(GlyphIndex index, LineIndex& lineIndex, LineRun*& lineRun, GlyphIndex& lastGlyphOfLine, Length numberOfLines, bool& isLastLine)
38 {
39   if(index == lastGlyphOfLine)
40   {
41     ++lineIndex;
42     if(lineIndex < numberOfLines)
43     {
44       isLastLine = (lineIndex + 1 == numberOfLines);
45       ++lineRun;
46       return true;
47     }
48   }
49
50   return false;
51 }
52
53 void UpdateLineInfo(const LineRun* lineRun, float& currentLineOffset, float& currentLineHeight, GlyphIndex& lastGlyphOfLine, bool isLastLine)
54 {
55   lastGlyphOfLine   = lineRun->glyphRun.glyphIndex + lineRun->glyphRun.numberOfGlyphs - 1u;
56   currentLineOffset = currentLineOffset + currentLineHeight;
57   currentLineHeight = GetLineHeight(*lineRun, isLastLine);
58 }
59
60 void GetTextGeometry(ModelPtr textModel, CharacterIndex startIndex, CharacterIndex endIndex, Vector<Vector2>& sizesList, Vector<Vector2>& positionsList)
61 {
62   VisualModelPtr&  visualModel  = textModel->mVisualModel;
63   LogicalModelPtr& logicalModel = textModel->mLogicalModel;
64
65   const GlyphIndex* const         charactersToGlyphBuffer        = visualModel->mCharactersToGlyph.Begin();
66   const Length* const             glyphsPerCharacterBuffer       = visualModel->mGlyphsPerCharacter.Begin();
67   const GlyphInfo* const          glyphsBuffer                   = visualModel->mGlyphs.Begin();
68   const Vector2* const            positionsBuffer                = visualModel->mGlyphPositions.Begin();
69   const Length* const             charactersPerGlyphBuffer       = visualModel->mCharactersPerGlyph.Begin();
70   const CharacterIndex* const     glyphToCharacterBuffer         = visualModel->mGlyphsToCharacters.Begin();
71   const CharacterDirection* const modelCharacterDirectionsBuffer = (0u != logicalModel->mCharacterDirections.Count()) ? logicalModel->mCharacterDirections.Begin() : NULL;
72
73   //Clear the lists
74   sizesList.Clear();
75   positionsList.Clear();
76
77   if(charactersToGlyphBuffer == nullptr || glyphsPerCharacterBuffer  == nullptr || charactersPerGlyphBuffer  == nullptr || glyphToCharacterBuffer == nullptr )
78   {
79     return;
80   }
81
82   if(startIndex >= logicalModel->mText.Count() && endIndex >= logicalModel->mText.Count())
83   {
84     return;
85   }
86
87   if(startIndex >= logicalModel->mText.Count())
88   {
89     startIndex = static_cast<CharacterIndex>(logicalModel->mText.Count() - 1u);
90   }
91
92   if(endIndex >= logicalModel->mText.Count())
93   {
94     endIndex = static_cast<CharacterIndex>(logicalModel->mText.Count() - 1u);
95   }
96
97   if(startIndex > endIndex)
98   {
99     std::swap(startIndex, endIndex);
100   }
101
102   LineRun*   lineRun    = visualModel->mLines.Begin();
103   GlyphIndex glyphStart = *(charactersToGlyphBuffer + startIndex);
104
105   //if glyph not in the first line (in some ellipsis cases)
106   if(glyphStart < lineRun->glyphRun.glyphIndex)
107   {
108     glyphStart = lineRun->glyphRun.glyphIndex;
109     startIndex = *(glyphToCharacterBuffer + glyphStart);
110
111     if(startIndex > endIndex)
112     {
113       std::swap(startIndex, endIndex);
114     }
115   }
116
117   const Length numberOfGlyphs = *(glyphsPerCharacterBuffer + endIndex);
118   GlyphIndex   glyphEnd       = *(charactersToGlyphBuffer + endIndex) + ((numberOfGlyphs > 0) ? numberOfGlyphs - 1u : 0u);
119   LineIndex    lineIndex      = visualModel->GetLineOfCharacter(startIndex);
120   Length       numberOfLines  = visualModel->GetTotalNumberOfLines();
121   bool         isLastLine     = lineIndex + 1 == numberOfLines;
122
123   LineIndex firstLineIndex = lineIndex;
124   Size      textInLineSize;
125   Vector2   textInLinePosition;
126
127   lineRun += firstLineIndex;
128
129   //get the first line and its vertical offset
130   float      currentLineOffset = CalculateLineOffset(visualModel->mLines, firstLineIndex);
131   float      currentLineHeight = GetLineHeight(*lineRun, isLastLine);
132   GlyphIndex lastGlyphOfLine   = lineRun->glyphRun.glyphIndex + lineRun->glyphRun.numberOfGlyphs - 1;
133
134   // Check if the first/last glyph is a ligature that needs be splitted like English fi or Arabic ﻻ.
135   const Length numberOfCharactersStart = *(charactersPerGlyphBuffer + glyphStart);
136   const Length numberOfCharactersEnd   = *(charactersPerGlyphBuffer + glyphEnd);
137
138   bool splitStartGlyph = (numberOfCharactersStart > 1u) && HasLigatureMustBreak(logicalModel->GetScript(startIndex));
139   bool splitEndGlyph   = (glyphStart != glyphEnd) && (numberOfCharactersEnd > 1u) && HasLigatureMustBreak(logicalModel->GetScript(endIndex));
140
141   Vector2            currentSize;
142   Vector2            currentPosition;
143   Vector2            blockSize;
144   Vector2            blockPos;
145   CharacterDirection isCurrentRightToLeft;
146
147   CharacterDirection                      isPrevoiusRightToLeft           = (nullptr != modelCharacterDirectionsBuffer ? *(modelCharacterDirectionsBuffer + startIndex) : false);
148   const bool                              isEllipsisEnabled               = textModel->mElideEnabled;
149   const GlyphIndex                        startIndexOfGlyphs              = textModel->GetStartIndexOfElidedGlyphs();
150   const GlyphIndex                        endIndexOfGlyphs                = textModel->GetEndIndexOfElidedGlyphs();
151   const GlyphIndex                        firstMiddleIndexOfElidedGlyphs  = textModel->GetFirstMiddleIndexOfElidedGlyphs();
152   const GlyphIndex                        secondMiddleIndexOfElidedGlyphs = textModel->GetSecondMiddleIndexOfElidedGlyphs();
153   const DevelText::EllipsisPosition::Type ellipsisPosition                = textModel->GetEllipsisPosition();
154
155   for(GlyphIndex index = glyphStart; index <= glyphEnd; ++index)
156   {
157     if(isEllipsisEnabled)
158     {
159       if(ellipsisPosition == DevelText::EllipsisPosition::MIDDLE)
160       {
161         if(index >= firstMiddleIndexOfElidedGlyphs &&
162            index < secondMiddleIndexOfElidedGlyphs)
163         {
164           if((index - 1 == firstMiddleIndexOfElidedGlyphs) && (firstMiddleIndexOfElidedGlyphs != 0))
165           {
166             sizesList.PushBack(blockSize);
167             positionsList.PushBack(blockPos);
168           }
169
170           if(GetNextLine(index, lineIndex, lineRun, lastGlyphOfLine, numberOfLines, isLastLine))
171           {
172             UpdateLineInfo(lineRun, currentLineOffset, currentLineHeight, lastGlyphOfLine, isLastLine);
173           }
174           // Ignore any glyph that was removed
175           continue;
176         }
177       }
178       else
179       {
180         if((ellipsisPosition == DevelText::EllipsisPosition::END) && (index > endIndexOfGlyphs))
181         {
182           //skip remaining elided glyphs
183           break;
184         }
185         else if((ellipsisPosition == DevelText::EllipsisPosition::START) && (index <= startIndexOfGlyphs))
186         {
187           if(GetNextLine(index, lineIndex, lineRun, lastGlyphOfLine, numberOfLines, isLastLine))
188           {
189             UpdateLineInfo(lineRun, currentLineOffset, currentLineHeight, lastGlyphOfLine, isLastLine);
190           }
191
192           continue;
193         }
194       }
195     }
196
197     const GlyphInfo& glyph    = *(glyphsBuffer + index);
198     const Vector2&   position = *(positionsBuffer + index);
199
200     // If NULL, means all of the characters is left to right.
201     isCurrentRightToLeft = (nullptr != modelCharacterDirectionsBuffer ? *(modelCharacterDirectionsBuffer + *(glyphToCharacterBuffer + index)) : false);
202
203     if(splitStartGlyph && (index == glyphStart))
204     {
205       // If the first glyph is a ligature that needs to be splitted, we may need only to add part of the glyph.
206       const float          glyphAdvance       = glyph.advance / static_cast<float>(numberOfCharactersStart);
207       const CharacterIndex interGlyphIndex    = startIndex - *(glyphToCharacterBuffer + glyphStart);
208       const Length         numberOfCharacters = (glyphStart == glyphEnd) ? (endIndex - startIndex) + 1 : (numberOfCharactersStart - interGlyphIndex);
209
210       currentPosition.x = lineRun->alignmentOffset + position.x - glyph.xBearing + textModel->mScrollPosition.x + glyphAdvance * static_cast<float>(isCurrentRightToLeft ? (numberOfCharactersStart - interGlyphIndex - numberOfCharacters) : interGlyphIndex);
211       currentPosition.y = currentLineOffset + textModel->mScrollPosition.y;
212       currentSize.x     = static_cast<float>(numberOfCharacters) * glyphAdvance;
213       currentSize.y     = currentLineHeight;
214       splitStartGlyph   = false;
215     }
216     else if(splitEndGlyph && (index == glyphEnd))
217     {
218       const float          glyphAdvance       = glyph.advance / static_cast<float>(numberOfCharactersEnd);
219       const CharacterIndex interGlyphIndex    = endIndex - *(glyphToCharacterBuffer + glyphEnd);
220       const Length         numberOfCharacters = numberOfCharactersEnd - interGlyphIndex - 1;
221
222       currentPosition.x = lineRun->alignmentOffset + position.x - glyph.xBearing + textModel->mScrollPosition.x + (isCurrentRightToLeft ? (glyphAdvance * static_cast<float>(numberOfCharacters)) : 0.f);
223       currentPosition.y = currentLineOffset + textModel->mScrollPosition.y;
224       currentSize.x     = static_cast<float>(interGlyphIndex + 1) * glyphAdvance;
225       currentSize.y     = currentLineHeight;
226       splitEndGlyph     = false;
227     }
228     else
229     {
230       currentPosition.x = lineRun->alignmentOffset + position.x - glyph.xBearing + textModel->mScrollPosition.x;
231       currentPosition.y = currentLineOffset + textModel->mScrollPosition.y;
232       currentSize.x     = glyph.advance;
233       currentSize.y     = currentLineHeight;
234
235       // if there is next line to retrieve.
236       if(GetNextLine(index, lineIndex, lineRun, lastGlyphOfLine, numberOfLines, isLastLine))
237       {
238         UpdateLineInfo(lineRun, currentLineOffset, currentLineHeight, lastGlyphOfLine, isLastLine);
239       }
240     }
241
242     if((index == glyphStart) || (isEllipsisEnabled && (((ellipsisPosition == DevelText::EllipsisPosition::MIDDLE) && (index == secondMiddleIndexOfElidedGlyphs)) || ((ellipsisPosition == DevelText::EllipsisPosition::START) && (index - 1 == startIndexOfGlyphs)))))
243     {
244       blockPos  = currentPosition;
245       blockSize = currentSize;
246     }
247     else if((isPrevoiusRightToLeft != isCurrentRightToLeft) || (blockPos.y != currentPosition.y)) //new direction or new line
248     {
249       sizesList.PushBack(blockSize);
250       positionsList.PushBack(blockPos);
251
252       blockPos  = currentPosition;
253       blockSize = currentSize;
254     }
255     else
256     {
257       if(isCurrentRightToLeft)
258       {
259         blockPos.x -= currentSize.x;
260       }
261
262       blockSize.x += currentSize.x;
263     }
264
265     isPrevoiusRightToLeft = isCurrentRightToLeft;
266   }
267
268   //add last block
269   sizesList.PushBack(blockSize);
270   positionsList.PushBack(blockPos);
271 }
272
273 float GetLineLeft(const LineRun& lineRun)
274 {
275   return lineRun.alignmentOffset;
276 }
277
278 float GetLineTop(const Vector<LineRun>& lines, const LineRun& lineRun)
279 {
280   float lineTop = 0;
281   const int numberOfLines = (int)lines.Count();
282
283   int currentLineIndex = 0;
284   Vector<LineRun>::ConstIterator endIt = (&lineRun);
285   for(Vector<LineRun>::Iterator it = lines.Begin();
286       it != endIt;
287       ++it, ++currentLineIndex)
288     {
289       LineRun& line = *it;
290       bool isLastLine  = (currentLineIndex + 1) == numberOfLines;
291       lineTop         += GetLineHeight(line, isLastLine);
292     }
293
294   return lineTop;
295 }
296
297 float GetLineWidth(const LineRun& lineRun)
298 {
299   return lineRun.width;
300 }
301
302 Rect<float> GetLineBoundingRect(ModelPtr textModel, const uint32_t lineIndex)
303 {
304
305   if(textModel->mVisualModel == nullptr)
306   {
307     return {0, 0, 0, 0};
308   }
309
310   Length numberOfLines = textModel->mVisualModel->GetTotalNumberOfLines();
311
312   if(lineIndex >= numberOfLines)
313   {
314     return {0, 0, 0, 0};
315   }
316
317   const Vector<LineRun>& lines   = textModel->mVisualModel->mLines;
318   const LineRun&         lineRun = lines[lineIndex];
319   bool  isFirstLine = lineIndex == 0;
320   bool  isLastLine  = (lineIndex + 1) == numberOfLines;
321
322   // Calculate the Left(lineX) = X position.
323   float lineX = GetLineLeft(lineRun) + textModel->mScrollPosition.x;
324
325   // Calculate the Top(lineY) = PreviousHeights.
326   // If the line is the first line of the text; its top = 0.
327   float lineY = (isFirstLine ? 0 : GetLineTop(lines, lineRun)) + textModel->mScrollPosition.y;
328
329   // The rectangle contains the width and height:
330   float lineWidth  = GetLineWidth(lineRun);
331   float lineHeight = GetLineHeight(lineRun, isLastLine);
332
333   return {lineX, lineY, lineWidth, lineHeight};
334 }
335
336 } // namespace Text
337
338 } // namespace Toolkit
339
340 } // namespace Dali