[dali_2.3.20] Merge branch '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 #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 #include <algorithm>
30
31 using namespace Dali;
32
33 namespace Dali
34 {
35 namespace Toolkit
36 {
37 namespace Text
38 {
39 bool GetNextLine(GlyphIndex index, LineIndex& lineIndex, LineRun*& lineRun, GlyphIndex& lastGlyphOfLine, Length numberOfLines, bool& isLastLine)
40 {
41   if(index == lastGlyphOfLine)
42   {
43     ++lineIndex;
44     if(lineIndex < numberOfLines)
45     {
46       isLastLine = (lineIndex + 1 == numberOfLines);
47       ++lineRun;
48       return true;
49     }
50   }
51
52   return false;
53 }
54
55 void UpdateLineInfo(const LineRun* lineRun, float& currentLineOffset, float& currentLineHeight, GlyphIndex& lastGlyphOfLine, bool isLastLine)
56 {
57   lastGlyphOfLine   = lineRun->glyphRun.glyphIndex + lineRun->glyphRun.numberOfGlyphs - 1u;
58   currentLineOffset = currentLineOffset + currentLineHeight;
59   currentLineHeight = GetLineHeight(*lineRun, isLastLine);
60 }
61
62 void GetTextGeometry(ModelPtr textModel, CharacterIndex startIndex, CharacterIndex endIndex, Vector<Vector2>& sizesList, Vector<Vector2>& positionsList)
63 {
64   VisualModelPtr&  visualModel  = textModel->mVisualModel;
65   LogicalModelPtr& logicalModel = textModel->mLogicalModel;
66
67   const GlyphIndex* const         charactersToGlyphBuffer        = visualModel->mCharactersToGlyph.Begin();
68   const Length* const             glyphsPerCharacterBuffer       = visualModel->mGlyphsPerCharacter.Begin();
69   const GlyphInfo* const          glyphsBuffer                   = visualModel->mGlyphs.Begin();
70   const Vector2* const            positionsBuffer                = visualModel->mGlyphPositions.Begin();
71   const Length* const             charactersPerGlyphBuffer       = visualModel->mCharactersPerGlyph.Begin();
72   const CharacterIndex* const     glyphToCharacterBuffer         = visualModel->mGlyphsToCharacters.Begin();
73   const CharacterDirection* const modelCharacterDirectionsBuffer = (0u != logicalModel->mCharacterDirections.Count()) ? logicalModel->mCharacterDirections.Begin() : NULL;
74
75   //Clear the lists
76   sizesList.Clear();
77   positionsList.Clear();
78
79   if(charactersToGlyphBuffer == nullptr || glyphsPerCharacterBuffer == nullptr || charactersPerGlyphBuffer == nullptr || glyphToCharacterBuffer == nullptr)
80   {
81     return;
82   }
83
84   if(startIndex >= logicalModel->mText.Count() && endIndex >= logicalModel->mText.Count())
85   {
86     return;
87   }
88
89   if(startIndex >= logicalModel->mText.Count())
90   {
91     startIndex = static_cast<CharacterIndex>(logicalModel->mText.Count() - 1u);
92   }
93
94   if(endIndex >= logicalModel->mText.Count())
95   {
96     endIndex = static_cast<CharacterIndex>(logicalModel->mText.Count() - 1u);
97   }
98
99   if(startIndex > endIndex)
100   {
101     std::swap(startIndex, endIndex);
102   }
103
104   LineRun*   lineRun    = visualModel->mLines.Begin();
105   GlyphIndex glyphStart = *(charactersToGlyphBuffer + startIndex);
106
107   //if glyph not in the first line (in some ellipsis cases)
108   if(glyphStart < lineRun->glyphRun.glyphIndex)
109   {
110     glyphStart = lineRun->glyphRun.glyphIndex;
111     startIndex = *(glyphToCharacterBuffer + glyphStart);
112
113     if(startIndex > endIndex)
114     {
115       std::swap(startIndex, endIndex);
116     }
117   }
118
119   const Length numberOfGlyphs = *(glyphsPerCharacterBuffer + endIndex);
120   GlyphIndex   glyphEnd       = *(charactersToGlyphBuffer + endIndex) + ((numberOfGlyphs > 0) ? numberOfGlyphs - 1u : 0u);
121   LineIndex    lineIndex      = visualModel->GetLineOfCharacter(startIndex);
122   Length       numberOfLines  = visualModel->GetTotalNumberOfLines();
123   bool         isLastLine     = lineIndex + 1 == numberOfLines;
124
125   LineIndex firstLineIndex = lineIndex;
126   Size      textInLineSize;
127   Vector2   textInLinePosition;
128
129   lineRun += firstLineIndex;
130
131   //get the first line and its vertical offset
132   float      currentLineOffset = CalculateLineOffset(visualModel->mLines, firstLineIndex);
133   float      currentLineHeight = GetLineHeight(*lineRun, isLastLine);
134   GlyphIndex lastGlyphOfLine   = lineRun->glyphRun.glyphIndex + lineRun->glyphRun.numberOfGlyphs - 1;
135
136   // Check if the first/last glyph is a ligature that needs be splitted like English fi or Arabic ﻻ.
137   const Length numberOfCharactersStart = *(charactersPerGlyphBuffer + glyphStart);
138   const Length numberOfCharactersEnd   = *(charactersPerGlyphBuffer + glyphEnd);
139
140   bool splitStartGlyph = (numberOfCharactersStart > 1u) && HasLigatureMustBreak(logicalModel->GetScript(startIndex));
141   bool splitEndGlyph   = (glyphStart != glyphEnd) && (numberOfCharactersEnd > 1u) && HasLigatureMustBreak(logicalModel->GetScript(endIndex));
142
143   Vector2            currentSize;
144   Vector2            currentPosition;
145   Vector2            blockSize;
146   Vector2            blockPos;
147   CharacterDirection isCurrentRightToLeft;
148
149   CharacterDirection                      isPrevoiusRightToLeft           = (nullptr != modelCharacterDirectionsBuffer ? *(modelCharacterDirectionsBuffer + startIndex) : false);
150   const bool                              isEllipsisEnabled               = textModel->mElideEnabled;
151   const GlyphIndex                        startIndexOfGlyphs              = textModel->GetStartIndexOfElidedGlyphs();
152   const GlyphIndex                        endIndexOfGlyphs                = textModel->GetEndIndexOfElidedGlyphs();
153   const GlyphIndex                        firstMiddleIndexOfElidedGlyphs  = textModel->GetFirstMiddleIndexOfElidedGlyphs();
154   const GlyphIndex                        secondMiddleIndexOfElidedGlyphs = textModel->GetSecondMiddleIndexOfElidedGlyphs();
155   const DevelText::EllipsisPosition::Type ellipsisPosition                = textModel->GetEllipsisPosition();
156
157   for(GlyphIndex index = glyphStart; index <= glyphEnd; ++index)
158   {
159     if(isEllipsisEnabled)
160     {
161       if(ellipsisPosition == DevelText::EllipsisPosition::MIDDLE)
162       {
163         if(index >= firstMiddleIndexOfElidedGlyphs &&
164            index < secondMiddleIndexOfElidedGlyphs)
165         {
166           if((index - 1 == firstMiddleIndexOfElidedGlyphs) && (firstMiddleIndexOfElidedGlyphs != 0))
167           {
168             sizesList.PushBack(blockSize);
169             positionsList.PushBack(blockPos);
170           }
171
172           if(GetNextLine(index, lineIndex, lineRun, lastGlyphOfLine, numberOfLines, isLastLine))
173           {
174             UpdateLineInfo(lineRun, currentLineOffset, currentLineHeight, lastGlyphOfLine, isLastLine);
175           }
176           // Ignore any glyph that was removed
177           continue;
178         }
179       }
180       else
181       {
182         if((ellipsisPosition == DevelText::EllipsisPosition::END) && (index > endIndexOfGlyphs))
183         {
184           //skip remaining elided glyphs
185           break;
186         }
187         else if((ellipsisPosition == DevelText::EllipsisPosition::START) && (index <= startIndexOfGlyphs))
188         {
189           if(GetNextLine(index, lineIndex, lineRun, lastGlyphOfLine, numberOfLines, isLastLine))
190           {
191             UpdateLineInfo(lineRun, currentLineOffset, currentLineHeight, lastGlyphOfLine, isLastLine);
192           }
193
194           continue;
195         }
196       }
197     }
198
199     const GlyphInfo& glyph    = *(glyphsBuffer + index);
200     const Vector2&   position = *(positionsBuffer + index);
201
202     // If NULL, means all of the characters is left to right.
203     isCurrentRightToLeft = (nullptr != modelCharacterDirectionsBuffer ? *(modelCharacterDirectionsBuffer + *(glyphToCharacterBuffer + index)) : false);
204
205     if(splitStartGlyph && (index == glyphStart))
206     {
207       // If the first glyph is a ligature that needs to be splitted, we may need only to add part of the glyph.
208       const float          glyphAdvance       = glyph.advance / static_cast<float>(numberOfCharactersStart);
209       const CharacterIndex interGlyphIndex    = startIndex - *(glyphToCharacterBuffer + glyphStart);
210       const Length         numberOfCharacters = (glyphStart == glyphEnd) ? (endIndex - startIndex) + 1 : (numberOfCharactersStart - interGlyphIndex);
211
212       currentPosition.x = lineRun->alignmentOffset + position.x - glyph.xBearing + textModel->mScrollPosition.x + glyphAdvance * static_cast<float>(isCurrentRightToLeft ? (numberOfCharactersStart - interGlyphIndex - numberOfCharacters) : interGlyphIndex);
213       currentPosition.y = currentLineOffset + textModel->mScrollPosition.y;
214       currentSize.x     = static_cast<float>(numberOfCharacters) * glyphAdvance;
215       currentSize.y     = currentLineHeight;
216       splitStartGlyph   = false;
217     }
218     else if(splitEndGlyph && (index == glyphEnd))
219     {
220       const float          glyphAdvance       = glyph.advance / static_cast<float>(numberOfCharactersEnd);
221       const CharacterIndex interGlyphIndex    = endIndex - *(glyphToCharacterBuffer + glyphEnd);
222       const Length         numberOfCharacters = numberOfCharactersEnd - interGlyphIndex - 1;
223
224       currentPosition.x = lineRun->alignmentOffset + position.x - glyph.xBearing + textModel->mScrollPosition.x + (isCurrentRightToLeft ? (glyphAdvance * static_cast<float>(numberOfCharacters)) : 0.f);
225       currentPosition.y = currentLineOffset + textModel->mScrollPosition.y;
226       currentSize.x     = static_cast<float>(interGlyphIndex + 1) * glyphAdvance;
227       currentSize.y     = currentLineHeight;
228       splitEndGlyph     = false;
229     }
230     else
231     {
232       currentPosition.x = lineRun->alignmentOffset + position.x - glyph.xBearing + textModel->mScrollPosition.x;
233       currentPosition.y = currentLineOffset + textModel->mScrollPosition.y;
234       currentSize.x     = glyph.advance;
235       currentSize.y     = currentLineHeight;
236
237       // if there is next line to retrieve.
238       if(GetNextLine(index, lineIndex, lineRun, lastGlyphOfLine, numberOfLines, isLastLine))
239       {
240         UpdateLineInfo(lineRun, currentLineOffset, currentLineHeight, lastGlyphOfLine, isLastLine);
241       }
242     }
243
244     if((index == glyphStart) || (isEllipsisEnabled && (((ellipsisPosition == DevelText::EllipsisPosition::MIDDLE) && (index == secondMiddleIndexOfElidedGlyphs)) || ((ellipsisPosition == DevelText::EllipsisPosition::START) && (index - 1 == startIndexOfGlyphs)))))
245     {
246       blockPos  = currentPosition;
247       blockSize = currentSize;
248     }
249     else if((isPrevoiusRightToLeft != isCurrentRightToLeft) || (!Dali::Equals(blockPos.y, currentPosition.y))) //new direction or new line
250     {
251       sizesList.PushBack(blockSize);
252       positionsList.PushBack(blockPos);
253
254       blockPos  = currentPosition;
255       blockSize = currentSize;
256     }
257     else
258     {
259       if(isCurrentRightToLeft)
260       {
261         blockPos.x -= currentSize.x;
262       }
263
264       blockSize.x += currentSize.x;
265     }
266
267     isPrevoiusRightToLeft = isCurrentRightToLeft;
268   }
269
270   //add last block
271   sizesList.PushBack(blockSize);
272   positionsList.PushBack(blockPos);
273 }
274
275 float GetLineLeft(const LineRun& lineRun)
276 {
277   return lineRun.alignmentOffset;
278 }
279
280 float GetLineTop(const Vector<LineRun>& lines, const LineRun& lineRun)
281 {
282   float     lineTop       = 0;
283   const int numberOfLines = (int)lines.Count();
284
285   int                            currentLineIndex = 0;
286   Vector<LineRun>::ConstIterator endIt            = (&lineRun);
287   for(Vector<LineRun>::Iterator it = lines.Begin();
288       it != endIt;
289       ++it, ++currentLineIndex)
290   {
291     LineRun& line       = *it;
292     bool     isLastLine = (currentLineIndex + 1) == numberOfLines;
293     lineTop += GetLineHeight(line, isLastLine);
294   }
295
296   return lineTop;
297 }
298
299 float GetLineWidth(const LineRun& lineRun)
300 {
301   return lineRun.width;
302 }
303
304 Rect<float> GetLineBoundingRect(ModelPtr textModel, const uint32_t lineIndex)
305 {
306   if(textModel->mVisualModel == nullptr)
307   {
308     return {0, 0, 0, 0};
309   }
310
311   Length numberOfLines = textModel->mVisualModel->GetTotalNumberOfLines();
312
313   if(lineIndex >= numberOfLines)
314   {
315     return {0, 0, 0, 0};
316   }
317
318   const Vector<LineRun>& lines       = textModel->mVisualModel->mLines;
319   const LineRun&         lineRun     = lines[lineIndex];
320   bool                   isFirstLine = lineIndex == 0;
321   bool                   isLastLine  = (lineIndex + 1) == numberOfLines;
322
323   // Calculate the Left(lineX) = X position.
324   float lineX = GetLineLeft(lineRun) + textModel->mScrollPosition.x;
325
326   // Calculate the Top(lineY) = PreviousHeights.
327   // If the line is the first line of the text; its top = 0.
328   float lineY = (isFirstLine ? 0 : GetLineTop(lines, lineRun)) + textModel->mScrollPosition.y;
329
330   // The rectangle contains the width and height:
331   float lineWidth  = GetLineWidth(lineRun);
332   float lineHeight = GetLineHeight(lineRun, isLastLine);
333
334   return {lineX, lineY, lineWidth, lineHeight};
335 }
336
337 float GetCharacterLeft(const GlyphInfo& glyph, const Vector2& characterPosition)
338 {
339   return characterPosition.x - glyph.xBearing;
340 }
341
342 float GetCharacterTop(const float& yPosition)
343 {
344   return (-1 * yPosition);
345 }
346
347 float GetCharacterHeight(const GlyphInfo& glyph)
348 {
349   return glyph.height;
350 }
351
352 float GetCharacterWidth(const GlyphInfo& glyph)
353 {
354   return glyph.advance;
355 }
356
357 Rect<> GetCharacterBoundingRect(ModelPtr textModel, const uint32_t charIndex)
358 {
359   if(textModel->mVisualModel == nullptr)
360   {
361     return {0, 0, 0, 0};
362   }
363
364   VisualModelPtr&  visualModel  = textModel->mVisualModel;
365   LogicalModelPtr& logicalModel = textModel->mLogicalModel;
366
367   if(charIndex >= logicalModel->mText.Count() || visualModel->mLines.Empty())
368   {
369     return {0, 0, 0, 0};
370   }
371
372   const Vector<Vector2>&   glyphPositions = visualModel->mGlyphPositions;
373   const Vector<GlyphInfo>& glyphs         = visualModel->mGlyphs;
374   const Vector<LineRun>&   lines          = visualModel->mLines;
375
376   //For each character, the index of the first glyph.
377   const GlyphIndex glyphIndex = visualModel->mCharactersToGlyph[charIndex]; //took its glyphs
378
379   const Vector2&   characterPosition = glyphPositions[glyphIndex];
380   const GlyphInfo& glyphInfo         = glyphs[glyphIndex];
381
382   // GetLineOfCharacter function returns 0 if the lines are empty
383   const int      lineIndex = visualModel->GetLineOfCharacter(charIndex);
384   const LineRun& lineRun   = lines[lineIndex];
385
386   //Calculate the left: x position of the glyph + alignmentOffset of the line +  mScrollPosition.x.
387   float characterX = lineRun.alignmentOffset + GetCharacterLeft(glyphInfo, characterPosition) + textModel->mScrollPosition.x;
388
389   //Calculate the Top(characterY): position.Y + previouse lines height + mScrollPosition.y.
390   bool isFirstLine = lineIndex == 0;
391
392   //If the line is the first line of the text; its top = 0.
393   float lineY = (isFirstLine ? 0 : GetLineTop(lines, lineRun));
394
395   float characterY = lineY + GetCharacterTop(characterPosition.y) + textModel->mScrollPosition.y;
396
397   //The rectangle contains the width and height:
398   float characterWidth  = GetCharacterWidth(glyphInfo);
399   float characterHeight = GetCharacterHeight(glyphInfo);
400
401   return {characterX, characterY, characterWidth, characterHeight};
402 }
403
404 int GetCharIndexAtPosition(ModelPtr textModel, float visualX, float visualY)
405 {
406   if(textModel == nullptr)
407   {
408     return -1;
409   }
410
411   VisualModelPtr& visualModel = textModel->mVisualModel;
412
413   const int totalNumberOfGlyphs = visualModel->mGlyphs.Count();
414   const int totalNumberOfLines  = visualModel->mLines.Count();
415
416   if((0 == totalNumberOfGlyphs) ||
417      (0 == totalNumberOfLines))
418   {
419     return -1;
420   }
421
422   //The top point of the view = 0.
423   if(visualY < 0)
424   {
425     return -1;
426   }
427
428   const Vector<LineRun>& lines = visualModel->mLines;
429
430   float lineTop   = 0.f;
431   int   lineIndex = 0;
432   int   high      = totalNumberOfLines;
433   int   low       = -1;
434   int   guess;
435
436   // Searching for the correct line.
437   while(high - low > 1)
438   {
439     guess = (high + low) / 2;
440     Vector<LineRun>::ConstIterator it = lines.Begin() + guess;
441
442     lineTop = GetLineTop(lines, *it);
443
444     if(lineTop > visualY)
445     {
446       high = guess;
447     }
448     else
449     {
450       low = guess;
451     }
452   }
453
454   if(low < 0)
455   {
456     lineIndex = 0;
457   }
458   else
459   {
460     lineIndex = low;
461   }
462
463   bool isLastLine = lineIndex + 1 == totalNumberOfLines;
464
465   if(isLastLine)
466   {
467     const LineRun& line = *(visualModel->mLines.Begin() + lineIndex);
468     float lineHeight = GetLineHeight(line, isLastLine);
469
470     // If the visualY is placed after the last line.
471     if(visualY > lineTop + lineHeight)
472     {
473       return -1;
474     }
475   }
476
477  // Start searching for the visualX
478   const LineRun& line = *(visualModel->mLines.Begin() + lineIndex);
479
480   visualX -= line.alignmentOffset;
481
482   // Positions of the glyphs
483   const Vector2* const positionsBuffer = visualModel->mGlyphPositions.Begin();
484
485   const CharacterIndex startCharacter = line.characterRun.characterIndex;
486   const CharacterIndex endCharacter   = line.characterRun.characterIndex + line.characterRun.numberOfCharacters - 1;
487
488   CharacterIndex characterIndexAtPosition = -1;
489   CharacterIndex characterIndex           = startCharacter;
490   float characterPosition;
491   float rightMostCharacterPosition;
492
493   for(; characterIndex != endCharacter; characterIndex++)
494   {
495     characterPosition = positionsBuffer[characterIndex].x;
496     rightMostCharacterPosition = positionsBuffer[characterIndex+1].x;
497
498     if(visualX < rightMostCharacterPosition && visualX >= characterPosition)
499     {
500       characterIndexAtPosition = characterIndex;
501       break;
502     }
503   }
504
505   if(characterIndex == endCharacter)
506   {
507     // If the visualX is within the last character position or it comes after the last character; we return the last character.
508     rightMostCharacterPosition = positionsBuffer[endCharacter].x + GetCharacterWidth(visualModel->mGlyphs[endCharacter]);
509
510     if(visualX >= positionsBuffer[endCharacter].x && visualX < rightMostCharacterPosition)
511     {
512       characterIndexAtPosition = endCharacter;
513     }
514   }
515
516   return characterIndexAtPosition;
517 }
518 } // namespace Text
519
520 } // namespace Toolkit
521
522 } // namespace Dali