Merge "Use Renderer::BlendMode::USE_ACTOR_OPACITY instead to use depth write mode...
[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 #include <dali/public-api/math/math-utils.h>
24
25 // INTERNAL INCLUDES
26 #include <dali-toolkit/internal/text/cursor-helper-functions.h>
27 #include <dali-toolkit/internal/text/line-run.h>
28 #include <dali-toolkit/internal/text/visual-model-impl.h>
29
30 using namespace Dali;
31
32 namespace Dali
33 {
34 namespace Toolkit
35 {
36 namespace Text
37 {
38 bool GetNextLine(GlyphIndex index, LineIndex& lineIndex, LineRun*& lineRun, GlyphIndex& lastGlyphOfLine, Length numberOfLines, bool& isLastLine)
39 {
40   if(index == lastGlyphOfLine)
41   {
42     ++lineIndex;
43     if(lineIndex < numberOfLines)
44     {
45       isLastLine = (lineIndex + 1 == numberOfLines);
46       ++lineRun;
47       return true;
48     }
49   }
50
51   return false;
52 }
53
54 void UpdateLineInfo(const LineRun* lineRun, float& currentLineOffset, float& currentLineHeight, GlyphIndex& lastGlyphOfLine, bool isLastLine)
55 {
56   lastGlyphOfLine   = lineRun->glyphRun.glyphIndex + lineRun->glyphRun.numberOfGlyphs - 1u;
57   currentLineOffset = currentLineOffset + currentLineHeight;
58   currentLineHeight = GetLineHeight(*lineRun, isLastLine);
59 }
60
61 void GetTextGeometry(ModelPtr textModel, CharacterIndex startIndex, CharacterIndex endIndex, Vector<Vector2>& sizesList, Vector<Vector2>& positionsList)
62 {
63   VisualModelPtr&  visualModel  = textModel->mVisualModel;
64   LogicalModelPtr& logicalModel = textModel->mLogicalModel;
65
66   const GlyphIndex* const         charactersToGlyphBuffer        = visualModel->mCharactersToGlyph.Begin();
67   const Length* const             glyphsPerCharacterBuffer       = visualModel->mGlyphsPerCharacter.Begin();
68   const GlyphInfo* const          glyphsBuffer                   = visualModel->mGlyphs.Begin();
69   const Vector2* const            positionsBuffer                = visualModel->mGlyphPositions.Begin();
70   const Length* const             charactersPerGlyphBuffer       = visualModel->mCharactersPerGlyph.Begin();
71   const CharacterIndex* const     glyphToCharacterBuffer         = visualModel->mGlyphsToCharacters.Begin();
72   const CharacterDirection* const modelCharacterDirectionsBuffer = (0u != logicalModel->mCharacterDirections.Count()) ? logicalModel->mCharacterDirections.Begin() : NULL;
73
74   //Clear the lists
75   sizesList.Clear();
76   positionsList.Clear();
77
78   if(charactersToGlyphBuffer == nullptr || glyphsPerCharacterBuffer == nullptr || charactersPerGlyphBuffer == nullptr || glyphToCharacterBuffer == nullptr)
79   {
80     return;
81   }
82
83   if(startIndex >= logicalModel->mText.Count() && endIndex >= logicalModel->mText.Count())
84   {
85     return;
86   }
87
88   if(startIndex >= logicalModel->mText.Count())
89   {
90     startIndex = static_cast<CharacterIndex>(logicalModel->mText.Count() - 1u);
91   }
92
93   if(endIndex >= logicalModel->mText.Count())
94   {
95     endIndex = static_cast<CharacterIndex>(logicalModel->mText.Count() - 1u);
96   }
97
98   if(startIndex > endIndex)
99   {
100     std::swap(startIndex, endIndex);
101   }
102
103   LineRun*   lineRun    = visualModel->mLines.Begin();
104   GlyphIndex glyphStart = *(charactersToGlyphBuffer + startIndex);
105
106   //if glyph not in the first line (in some ellipsis cases)
107   if(glyphStart < lineRun->glyphRun.glyphIndex)
108   {
109     glyphStart = lineRun->glyphRun.glyphIndex;
110     startIndex = *(glyphToCharacterBuffer + glyphStart);
111
112     if(startIndex > endIndex)
113     {
114       std::swap(startIndex, endIndex);
115     }
116   }
117
118   const Length numberOfGlyphs = *(glyphsPerCharacterBuffer + endIndex);
119   GlyphIndex   glyphEnd       = *(charactersToGlyphBuffer + endIndex) + ((numberOfGlyphs > 0) ? numberOfGlyphs - 1u : 0u);
120   LineIndex    lineIndex      = visualModel->GetLineOfCharacter(startIndex);
121   Length       numberOfLines  = visualModel->GetTotalNumberOfLines();
122   bool         isLastLine     = lineIndex + 1 == numberOfLines;
123
124   LineIndex firstLineIndex = lineIndex;
125   Size      textInLineSize;
126   Vector2   textInLinePosition;
127
128   lineRun += firstLineIndex;
129
130   //get the first line and its vertical offset
131   float      currentLineOffset = CalculateLineOffset(visualModel->mLines, firstLineIndex);
132   float      currentLineHeight = GetLineHeight(*lineRun, isLastLine);
133   GlyphIndex lastGlyphOfLine   = lineRun->glyphRun.glyphIndex + lineRun->glyphRun.numberOfGlyphs - 1;
134
135   // Check if the first/last glyph is a ligature that needs be splitted like English fi or Arabic ﻻ.
136   const Length numberOfCharactersStart = *(charactersPerGlyphBuffer + glyphStart);
137   const Length numberOfCharactersEnd   = *(charactersPerGlyphBuffer + glyphEnd);
138
139   bool splitStartGlyph = (numberOfCharactersStart > 1u) && HasLigatureMustBreak(logicalModel->GetScript(startIndex));
140   bool splitEndGlyph   = (glyphStart != glyphEnd) && (numberOfCharactersEnd > 1u) && HasLigatureMustBreak(logicalModel->GetScript(endIndex));
141
142   Vector2            currentSize;
143   Vector2            currentPosition;
144   Vector2            blockSize;
145   Vector2            blockPos;
146   CharacterDirection isCurrentRightToLeft;
147
148   CharacterDirection                      isPrevoiusRightToLeft           = (nullptr != modelCharacterDirectionsBuffer ? *(modelCharacterDirectionsBuffer + startIndex) : false);
149   const bool                              isEllipsisEnabled               = textModel->mElideEnabled;
150   const GlyphIndex                        startIndexOfGlyphs              = textModel->GetStartIndexOfElidedGlyphs();
151   const GlyphIndex                        endIndexOfGlyphs                = textModel->GetEndIndexOfElidedGlyphs();
152   const GlyphIndex                        firstMiddleIndexOfElidedGlyphs  = textModel->GetFirstMiddleIndexOfElidedGlyphs();
153   const GlyphIndex                        secondMiddleIndexOfElidedGlyphs = textModel->GetSecondMiddleIndexOfElidedGlyphs();
154   const DevelText::EllipsisPosition::Type ellipsisPosition                = textModel->GetEllipsisPosition();
155
156   for(GlyphIndex index = glyphStart; index <= glyphEnd; ++index)
157   {
158     if(isEllipsisEnabled)
159     {
160       if(ellipsisPosition == DevelText::EllipsisPosition::MIDDLE)
161       {
162         if(index >= firstMiddleIndexOfElidedGlyphs &&
163            index < secondMiddleIndexOfElidedGlyphs)
164         {
165           if((index - 1 == firstMiddleIndexOfElidedGlyphs) && (firstMiddleIndexOfElidedGlyphs != 0))
166           {
167             sizesList.PushBack(blockSize);
168             positionsList.PushBack(blockPos);
169           }
170
171           if(GetNextLine(index, lineIndex, lineRun, lastGlyphOfLine, numberOfLines, isLastLine))
172           {
173             UpdateLineInfo(lineRun, currentLineOffset, currentLineHeight, lastGlyphOfLine, isLastLine);
174           }
175           // Ignore any glyph that was removed
176           continue;
177         }
178       }
179       else
180       {
181         if((ellipsisPosition == DevelText::EllipsisPosition::END) && (index > endIndexOfGlyphs))
182         {
183           //skip remaining elided glyphs
184           break;
185         }
186         else if((ellipsisPosition == DevelText::EllipsisPosition::START) && (index <= startIndexOfGlyphs))
187         {
188           if(GetNextLine(index, lineIndex, lineRun, lastGlyphOfLine, numberOfLines, isLastLine))
189           {
190             UpdateLineInfo(lineRun, currentLineOffset, currentLineHeight, lastGlyphOfLine, isLastLine);
191           }
192
193           continue;
194         }
195       }
196     }
197
198     const GlyphInfo& glyph    = *(glyphsBuffer + index);
199     const Vector2&   position = *(positionsBuffer + index);
200
201     // If NULL, means all of the characters is left to right.
202     isCurrentRightToLeft = (nullptr != modelCharacterDirectionsBuffer ? *(modelCharacterDirectionsBuffer + *(glyphToCharacterBuffer + index)) : false);
203
204     if(splitStartGlyph && (index == glyphStart))
205     {
206       // If the first glyph is a ligature that needs to be splitted, we may need only to add part of the glyph.
207       const float          glyphAdvance       = glyph.advance / static_cast<float>(numberOfCharactersStart);
208       const CharacterIndex interGlyphIndex    = startIndex - *(glyphToCharacterBuffer + glyphStart);
209       const Length         numberOfCharacters = (glyphStart == glyphEnd) ? (endIndex - startIndex) + 1 : (numberOfCharactersStart - interGlyphIndex);
210
211       currentPosition.x = lineRun->alignmentOffset + position.x - glyph.xBearing + textModel->mScrollPosition.x + glyphAdvance * static_cast<float>(isCurrentRightToLeft ? (numberOfCharactersStart - interGlyphIndex - numberOfCharacters) : interGlyphIndex);
212       currentPosition.y = currentLineOffset + textModel->mScrollPosition.y;
213       currentSize.x     = static_cast<float>(numberOfCharacters) * glyphAdvance;
214       currentSize.y     = currentLineHeight;
215       splitStartGlyph   = false;
216     }
217     else if(splitEndGlyph && (index == glyphEnd))
218     {
219       const float          glyphAdvance       = glyph.advance / static_cast<float>(numberOfCharactersEnd);
220       const CharacterIndex interGlyphIndex    = endIndex - *(glyphToCharacterBuffer + glyphEnd);
221       const Length         numberOfCharacters = numberOfCharactersEnd - interGlyphIndex - 1;
222
223       currentPosition.x = lineRun->alignmentOffset + position.x - glyph.xBearing + textModel->mScrollPosition.x + (isCurrentRightToLeft ? (glyphAdvance * static_cast<float>(numberOfCharacters)) : 0.f);
224       currentPosition.y = currentLineOffset + textModel->mScrollPosition.y;
225       currentSize.x     = static_cast<float>(interGlyphIndex + 1) * glyphAdvance;
226       currentSize.y     = currentLineHeight;
227       splitEndGlyph     = false;
228     }
229     else
230     {
231       currentPosition.x = lineRun->alignmentOffset + position.x - glyph.xBearing + textModel->mScrollPosition.x;
232       currentPosition.y = currentLineOffset + textModel->mScrollPosition.y;
233       currentSize.x     = glyph.advance;
234       currentSize.y     = currentLineHeight;
235
236       // if there is next line to retrieve.
237       if(GetNextLine(index, lineIndex, lineRun, lastGlyphOfLine, numberOfLines, isLastLine))
238       {
239         UpdateLineInfo(lineRun, currentLineOffset, currentLineHeight, lastGlyphOfLine, isLastLine);
240       }
241     }
242
243     if((index == glyphStart) || (isEllipsisEnabled && (((ellipsisPosition == DevelText::EllipsisPosition::MIDDLE) && (index == secondMiddleIndexOfElidedGlyphs)) || ((ellipsisPosition == DevelText::EllipsisPosition::START) && (index - 1 == startIndexOfGlyphs)))))
244     {
245       blockPos  = currentPosition;
246       blockSize = currentSize;
247     }
248     else if((isPrevoiusRightToLeft != isCurrentRightToLeft) || (!Dali::Equals(blockPos.y, currentPosition.y))) //new direction or new line
249     {
250       sizesList.PushBack(blockSize);
251       positionsList.PushBack(blockPos);
252
253       blockPos  = currentPosition;
254       blockSize = currentSize;
255     }
256     else
257     {
258       if(isCurrentRightToLeft)
259       {
260         blockPos.x -= currentSize.x;
261       }
262
263       blockSize.x += currentSize.x;
264     }
265
266     isPrevoiusRightToLeft = isCurrentRightToLeft;
267   }
268
269   //add last block
270   sizesList.PushBack(blockSize);
271   positionsList.PushBack(blockPos);
272 }
273
274 float GetLineLeft(const LineRun& lineRun)
275 {
276   return lineRun.alignmentOffset;
277 }
278
279 float GetLineTop(const Vector<LineRun>& lines, const LineRun& lineRun)
280 {
281   float     lineTop       = 0;
282   const int numberOfLines = (int)lines.Count();
283
284   int                            currentLineIndex = 0;
285   Vector<LineRun>::ConstIterator endIt            = (&lineRun);
286   for(Vector<LineRun>::Iterator it = lines.Begin();
287       it != endIt;
288       ++it, ++currentLineIndex)
289   {
290     LineRun& line       = *it;
291     bool     isLastLine = (currentLineIndex + 1) == numberOfLines;
292     lineTop += GetLineHeight(line, isLastLine);
293   }
294
295   return lineTop;
296 }
297
298 float GetLineWidth(const LineRun& lineRun)
299 {
300   return lineRun.width;
301 }
302
303 Rect<float> GetLineBoundingRect(ModelPtr textModel, const uint32_t lineIndex)
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 float GetCharacterLeft(const GlyphInfo& glyph, const Vector2& characterPosition)
337 {
338   return characterPosition.x - glyph.xBearing;
339 }
340
341 float GetCharacterTop(const float& yPosition)
342 {
343   return (-1 * yPosition);
344 }
345
346 float GetCharacterHeight(const GlyphInfo& glyph)
347 {
348   return glyph.height;
349 }
350
351 float GetCharacterWidth(const GlyphInfo& glyph)
352 {
353   return glyph.advance;
354 }
355
356 Rect<> GetCharacterBoundingRect(ModelPtr textModel, const uint32_t charIndex)
357 {
358   if(textModel->mVisualModel == nullptr)
359   {
360     return {0, 0, 0, 0};
361   }
362
363   VisualModelPtr&  visualModel  = textModel->mVisualModel;
364   LogicalModelPtr& logicalModel = textModel->mLogicalModel;
365
366   if(charIndex >= logicalModel->mText.Count() || visualModel->mLines.Empty())
367   {
368     return {0, 0, 0, 0};
369   }
370
371   const Vector<Vector2>&   glyphPositions = visualModel->mGlyphPositions;
372   const Vector<GlyphInfo>& glyphs         = visualModel->mGlyphs;
373   const Vector<LineRun>&   lines          = visualModel->mLines;
374
375   //For each character, the index of the first glyph.
376   const GlyphIndex glyphIndex = visualModel->mCharactersToGlyph[charIndex]; //took its glyphs
377
378   const Vector2&   characterPosition = glyphPositions[glyphIndex];
379   const GlyphInfo& glyphInfo         = glyphs[glyphIndex];
380
381   // GetLineOfCharacter function returns 0 if the lines are empty
382   const int      lineIndex = visualModel->GetLineOfCharacter(charIndex);
383   const LineRun& lineRun   = lines[lineIndex];
384
385   //Calculate the left: x position of the glyph + alignmentOffset of the line +  mScrollPosition.x.
386   float characterX = lineRun.alignmentOffset + GetCharacterLeft(glyphInfo, characterPosition) + textModel->mScrollPosition.x;
387
388   //Calculate the Top(characterY): position.Y + previouse lines height + mScrollPosition.y.
389   bool isFirstLine = lineIndex == 0;
390
391   //If the line is the first line of the text; its top = 0.
392   float lineY = (isFirstLine ? 0 : GetLineTop(lines, lineRun));
393
394   float characterY = lineY + GetCharacterTop(characterPosition.y) + textModel->mScrollPosition.y;
395
396   //The rectangle contains the width and height:
397   float characterWidth  = GetCharacterWidth(glyphInfo);
398   float characterHeight = GetCharacterHeight(glyphInfo);
399
400   return {characterX, characterY, characterWidth, characterHeight};
401 }
402
403 } // namespace Text
404
405 } // namespace Toolkit
406
407 } // namespace Dali