Merge "Fix nullptr for the text geometry." into devel/master
[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
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, bool& isLastLine)
36 {
37   if(index == lastGlyphOfLine)
38   {
39     ++lineIndex;
40     if(lineIndex < numberOfLines)
41     {
42       isLastLine = (lineIndex + 1 == numberOfLines);
43       ++lineRun;
44       return true;
45     }
46   }
47
48   return false;
49 }
50
51 void UpdateLineInfo(const LineRun* lineRun, float& currentLineOffset, float& currentLineHeight, GlyphIndex& lastGlyphOfLine, bool isLastLine)
52 {
53   lastGlyphOfLine   = lineRun->glyphRun.glyphIndex + lineRun->glyphRun.numberOfGlyphs - 1u;
54   currentLineOffset = currentLineOffset + currentLineHeight;
55   currentLineHeight = GetLineHeight(*lineRun, isLastLine);
56 }
57
58 void GetTextGeometry(ModelPtr textModel, CharacterIndex startIndex, CharacterIndex endIndex, Vector<Vector2>& sizesList, Vector<Vector2>& positionsList)
59 {
60   VisualModelPtr&  visualModel  = textModel->mVisualModel;
61   LogicalModelPtr& logicalModel = textModel->mLogicalModel;
62
63   const GlyphIndex* const         charactersToGlyphBuffer        = visualModel->mCharactersToGlyph.Begin();
64   const Length* const             glyphsPerCharacterBuffer       = visualModel->mGlyphsPerCharacter.Begin();
65   const GlyphInfo* const          glyphsBuffer                   = visualModel->mGlyphs.Begin();
66   const Vector2* const            positionsBuffer                = visualModel->mGlyphPositions.Begin();
67   const Length* const             charactersPerGlyphBuffer       = visualModel->mCharactersPerGlyph.Begin();
68   const CharacterIndex* const     glyphToCharacterBuffer         = visualModel->mGlyphsToCharacters.Begin();
69   const CharacterDirection* const modelCharacterDirectionsBuffer = (0u != logicalModel->mCharacterDirections.Count()) ? logicalModel->mCharacterDirections.Begin() : NULL;
70
71   //Clear the lists
72   sizesList.Clear();
73   positionsList.Clear();
74
75   if(charactersToGlyphBuffer == nullptr || glyphsPerCharacterBuffer  == nullptr || charactersPerGlyphBuffer  == nullptr || glyphToCharacterBuffer == nullptr )
76   {
77     return;
78   }
79
80   if(startIndex >= logicalModel->mText.Count() && endIndex >= logicalModel->mText.Count())
81   {
82     return;
83   }
84
85   if(startIndex >= logicalModel->mText.Count())
86   {
87     startIndex = logicalModel->mText.Count() - 1;
88   }
89
90   if(endIndex >= logicalModel->mText.Count())
91   {
92     endIndex = logicalModel->mText.Count() - 1;
93   }
94
95   if(startIndex > endIndex)
96   {
97     std::swap(startIndex, endIndex);
98   }
99
100   LineRun*   lineRun    = visualModel->mLines.Begin();
101   GlyphIndex glyphStart = *(charactersToGlyphBuffer + startIndex);
102
103   //if glyph not in the first line (in some ellipsis cases)
104   if(glyphStart < lineRun->glyphRun.glyphIndex)
105   {
106     glyphStart = lineRun->glyphRun.glyphIndex;
107     startIndex = *(glyphToCharacterBuffer + glyphStart);
108
109     if(startIndex > endIndex)
110     {
111       std::swap(startIndex, endIndex);
112     }
113   }
114
115   const Length numberOfGlyphs = *(glyphsPerCharacterBuffer + endIndex);
116   GlyphIndex   glyphEnd       = *(charactersToGlyphBuffer + endIndex) + ((numberOfGlyphs > 0) ? numberOfGlyphs - 1u : 0u);
117   LineIndex    lineIndex      = visualModel->GetLineOfCharacter(startIndex);
118   Length       numberOfLines  = visualModel->GetTotalNumberOfLines();
119   bool         isLastLine     = lineIndex + 1 == numberOfLines;
120
121   LineIndex firstLineIndex = lineIndex;
122   Size      textInLineSize;
123   Vector2   textInLinePosition;
124
125   lineRun += firstLineIndex;
126
127   //get the first line and its vertical offset
128   float      currentLineOffset = CalculateLineOffset(visualModel->mLines, firstLineIndex);
129   float      currentLineHeight = GetLineHeight(*lineRun, isLastLine);
130   GlyphIndex lastGlyphOfLine   = lineRun->glyphRun.glyphIndex + lineRun->glyphRun.numberOfGlyphs - 1;
131
132   // Check if the first/last glyph is a ligature that needs be splitted like English fi or Arabic ﻻ.
133   const Length numberOfCharactersStart = *(charactersPerGlyphBuffer + glyphStart);
134   const Length numberOfCharactersEnd   = *(charactersPerGlyphBuffer + glyphEnd);
135
136   bool splitStartGlyph = (numberOfCharactersStart > 1u) && HasLigatureMustBreak(logicalModel->GetScript(startIndex));
137   bool splitEndGlyph   = (glyphStart != glyphEnd) && (numberOfCharactersEnd > 1u) && HasLigatureMustBreak(logicalModel->GetScript(endIndex));
138
139   Vector2            currentSize;
140   Vector2            currentPosition;
141   Vector2            blockSize;
142   Vector2            blockPos;
143   CharacterDirection isCurrentRightToLeft;
144
145   CharacterDirection                      isPrevoiusRightToLeft           = (nullptr != modelCharacterDirectionsBuffer ? *(modelCharacterDirectionsBuffer + startIndex) : false);
146   const bool                              isEllipsisEnabled               = textModel->mElideEnabled;
147   const GlyphIndex                        startIndexOfGlyphs              = textModel->GetStartIndexOfElidedGlyphs();
148   const GlyphIndex                        endIndexOfGlyphs                = textModel->GetEndIndexOfElidedGlyphs();
149   const GlyphIndex                        firstMiddleIndexOfElidedGlyphs  = textModel->GetFirstMiddleIndexOfElidedGlyphs();
150   const GlyphIndex                        secondMiddleIndexOfElidedGlyphs = textModel->GetSecondMiddleIndexOfElidedGlyphs();
151   const DevelText::EllipsisPosition::Type ellipsisPosition                = textModel->GetEllipsisPosition();
152
153   for(GlyphIndex index = glyphStart; index <= glyphEnd; ++index)
154   {
155     if(isEllipsisEnabled)
156     {
157       if(ellipsisPosition == DevelText::EllipsisPosition::MIDDLE)
158       {
159         if(index >= firstMiddleIndexOfElidedGlyphs &&
160            index < secondMiddleIndexOfElidedGlyphs)
161         {
162           if((index - 1 == firstMiddleIndexOfElidedGlyphs) && (firstMiddleIndexOfElidedGlyphs != 0))
163           {
164             sizesList.PushBack(blockSize);
165             positionsList.PushBack(blockPos);
166           }
167
168           if(GetNextLine(index, lineIndex, lineRun, lastGlyphOfLine, numberOfLines, isLastLine))
169           {
170             UpdateLineInfo(lineRun, currentLineOffset, currentLineHeight, lastGlyphOfLine, isLastLine);
171           }
172           // Ignore any glyph that was removed
173           continue;
174         }
175       }
176       else
177       {
178         if((ellipsisPosition == DevelText::EllipsisPosition::END) && (index > endIndexOfGlyphs))
179         {
180           //skip remaining elided glyphs
181           break;
182         }
183         else if((ellipsisPosition == DevelText::EllipsisPosition::START) && (index <= startIndexOfGlyphs))
184         {
185           if(GetNextLine(index, lineIndex, lineRun, lastGlyphOfLine, numberOfLines, isLastLine))
186           {
187             UpdateLineInfo(lineRun, currentLineOffset, currentLineHeight, lastGlyphOfLine, isLastLine);
188           }
189
190           continue;
191         }
192       }
193     }
194
195     const GlyphInfo& glyph    = *(glyphsBuffer + index);
196     const Vector2&   position = *(positionsBuffer + index);
197
198     // If NULL, means all of the characters is left to right.
199     isCurrentRightToLeft = (nullptr != modelCharacterDirectionsBuffer ? *(modelCharacterDirectionsBuffer + *(glyphToCharacterBuffer + index)) : false);
200
201     if(splitStartGlyph && (index == glyphStart))
202     {
203       // If the first glyph is a ligature that needs to be splitted, we may need only to add part of the glyph.
204       const float          glyphAdvance       = glyph.advance / static_cast<float>(numberOfCharactersStart);
205       const CharacterIndex interGlyphIndex    = startIndex - *(glyphToCharacterBuffer + glyphStart);
206       const Length         numberOfCharacters = (glyphStart == glyphEnd) ? (endIndex - startIndex) + 1 : (numberOfCharactersStart - interGlyphIndex);
207
208       currentPosition.x = lineRun->alignmentOffset + position.x - glyph.xBearing + textModel->mScrollPosition.x + glyphAdvance * static_cast<float>(isCurrentRightToLeft ? (numberOfCharactersStart - interGlyphIndex - numberOfCharacters) : interGlyphIndex);
209       currentPosition.y = currentLineOffset + textModel->mScrollPosition.y;
210       currentSize.x     = static_cast<float>(numberOfCharacters) * glyphAdvance;
211       currentSize.y     = currentLineHeight;
212       splitStartGlyph   = false;
213     }
214     else if(splitEndGlyph && (index == glyphEnd))
215     {
216       const float          glyphAdvance       = glyph.advance / static_cast<float>(numberOfCharactersEnd);
217       const CharacterIndex interGlyphIndex    = endIndex - *(glyphToCharacterBuffer + glyphEnd);
218       const Length         numberOfCharacters = numberOfCharactersEnd - interGlyphIndex - 1;
219
220       currentPosition.x = lineRun->alignmentOffset + position.x - glyph.xBearing + textModel->mScrollPosition.x + (isCurrentRightToLeft ? (glyphAdvance * static_cast<float>(numberOfCharacters)) : 0.f);
221       currentPosition.y = currentLineOffset + textModel->mScrollPosition.y;
222       currentSize.x     = static_cast<float>(interGlyphIndex + 1) * glyphAdvance;
223       currentSize.y     = currentLineHeight;
224       splitEndGlyph     = false;
225     }
226     else
227     {
228       currentPosition.x = lineRun->alignmentOffset + position.x - glyph.xBearing + textModel->mScrollPosition.x;
229       currentPosition.y = currentLineOffset + textModel->mScrollPosition.y;
230       currentSize.x     = glyph.advance;
231       currentSize.y     = currentLineHeight;
232
233       // if there is next line to retrieve.
234       if(GetNextLine(index, lineIndex, lineRun, lastGlyphOfLine, numberOfLines, isLastLine))
235       {
236         UpdateLineInfo(lineRun, currentLineOffset, currentLineHeight, lastGlyphOfLine, isLastLine);
237       }
238     }
239
240     if((index == glyphStart) || (isEllipsisEnabled && (((ellipsisPosition == DevelText::EllipsisPosition::MIDDLE) && (index == secondMiddleIndexOfElidedGlyphs)) || ((ellipsisPosition == DevelText::EllipsisPosition::START) && (index - 1 == startIndexOfGlyphs)))))
241     {
242       blockPos  = currentPosition;
243       blockSize = currentSize;
244     }
245     else if((isPrevoiusRightToLeft != isCurrentRightToLeft) || (blockPos.y != currentPosition.y)) //new direction or new line
246     {
247       sizesList.PushBack(blockSize);
248       positionsList.PushBack(blockPos);
249
250       blockPos  = currentPosition;
251       blockSize = currentSize;
252     }
253     else
254     {
255       if(isCurrentRightToLeft)
256       {
257         blockPos.x -= currentSize.x;
258       }
259
260       blockSize.x += currentSize.x;
261     }
262
263     isPrevoiusRightToLeft = isCurrentRightToLeft;
264   }
265
266   //add last block
267   sizesList.PushBack(blockSize);
268   positionsList.PushBack(blockPos);
269 }
270
271 } // namespace Text
272
273 } // namespace Toolkit
274
275 } // namespace Dali