2 * Copyright (c) 2022 Samsung Electronics Co., Ltd.
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
19 #include <dali-toolkit/internal/text/text-geometry.h>
22 #include <dali/integration-api/debug.h>
23 #include <dali/public-api/math/math-utils.h>
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>
39 bool GetNextLine(GlyphIndex index, LineIndex& lineIndex, LineRun*& lineRun, GlyphIndex& lastGlyphOfLine, Length numberOfLines, bool& isLastLine)
41 if(index == lastGlyphOfLine)
44 if(lineIndex < numberOfLines)
46 isLastLine = (lineIndex + 1 == numberOfLines);
55 void UpdateLineInfo(const LineRun* lineRun, float& currentLineOffset, float& currentLineHeight, GlyphIndex& lastGlyphOfLine, bool isLastLine)
57 lastGlyphOfLine = lineRun->glyphRun.glyphIndex + lineRun->glyphRun.numberOfGlyphs - 1u;
58 currentLineOffset = currentLineOffset + currentLineHeight;
59 currentLineHeight = GetLineHeight(*lineRun, isLastLine);
62 void GetTextGeometry(ModelPtr textModel, CharacterIndex startIndex, CharacterIndex endIndex, Vector<Vector2>& sizesList, Vector<Vector2>& positionsList)
64 VisualModelPtr& visualModel = textModel->mVisualModel;
65 LogicalModelPtr& logicalModel = textModel->mLogicalModel;
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;
77 positionsList.Clear();
79 if(charactersToGlyphBuffer == nullptr || glyphsPerCharacterBuffer == nullptr || charactersPerGlyphBuffer == nullptr || glyphToCharacterBuffer == nullptr)
84 if(startIndex >= logicalModel->mText.Count() && endIndex >= logicalModel->mText.Count())
89 if(startIndex >= logicalModel->mText.Count())
91 startIndex = static_cast<CharacterIndex>(logicalModel->mText.Count() - 1u);
94 if(endIndex >= logicalModel->mText.Count())
96 endIndex = static_cast<CharacterIndex>(logicalModel->mText.Count() - 1u);
99 if(startIndex > endIndex)
101 std::swap(startIndex, endIndex);
104 LineRun* lineRun = visualModel->mLines.Begin();
105 GlyphIndex glyphStart = *(charactersToGlyphBuffer + startIndex);
107 //if glyph not in the first line (in some ellipsis cases)
108 if(glyphStart < lineRun->glyphRun.glyphIndex)
110 glyphStart = lineRun->glyphRun.glyphIndex;
111 startIndex = *(glyphToCharacterBuffer + glyphStart);
113 if(startIndex > endIndex)
115 std::swap(startIndex, endIndex);
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;
125 LineIndex firstLineIndex = lineIndex;
127 Vector2 textInLinePosition;
129 lineRun += firstLineIndex;
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;
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);
140 bool splitStartGlyph = (numberOfCharactersStart > 1u) && HasLigatureMustBreak(logicalModel->GetScript(startIndex));
141 bool splitEndGlyph = (glyphStart != glyphEnd) && (numberOfCharactersEnd > 1u) && HasLigatureMustBreak(logicalModel->GetScript(endIndex));
144 Vector2 currentPosition;
147 CharacterDirection isCurrentRightToLeft;
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();
157 for(GlyphIndex index = glyphStart; index <= glyphEnd; ++index)
159 if(isEllipsisEnabled)
161 if(ellipsisPosition == DevelText::EllipsisPosition::MIDDLE)
163 if(index >= firstMiddleIndexOfElidedGlyphs &&
164 index < secondMiddleIndexOfElidedGlyphs)
166 if((index - 1 == firstMiddleIndexOfElidedGlyphs) && (firstMiddleIndexOfElidedGlyphs != 0))
168 sizesList.PushBack(blockSize);
169 positionsList.PushBack(blockPos);
172 if(GetNextLine(index, lineIndex, lineRun, lastGlyphOfLine, numberOfLines, isLastLine))
174 UpdateLineInfo(lineRun, currentLineOffset, currentLineHeight, lastGlyphOfLine, isLastLine);
176 // Ignore any glyph that was removed
182 if((ellipsisPosition == DevelText::EllipsisPosition::END) && (index > endIndexOfGlyphs))
184 //skip remaining elided glyphs
187 else if((ellipsisPosition == DevelText::EllipsisPosition::START) && (index <= startIndexOfGlyphs))
189 if(GetNextLine(index, lineIndex, lineRun, lastGlyphOfLine, numberOfLines, isLastLine))
191 UpdateLineInfo(lineRun, currentLineOffset, currentLineHeight, lastGlyphOfLine, isLastLine);
199 const GlyphInfo& glyph = *(glyphsBuffer + index);
200 const Vector2& position = *(positionsBuffer + index);
202 // If NULL, means all of the characters is left to right.
203 isCurrentRightToLeft = (nullptr != modelCharacterDirectionsBuffer ? *(modelCharacterDirectionsBuffer + *(glyphToCharacterBuffer + index)) : false);
205 if(splitStartGlyph && (index == glyphStart))
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);
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;
218 else if(splitEndGlyph && (index == glyphEnd))
220 const float glyphAdvance = glyph.advance / static_cast<float>(numberOfCharactersEnd);
221 const CharacterIndex interGlyphIndex = endIndex - *(glyphToCharacterBuffer + glyphEnd);
222 const Length numberOfCharacters = numberOfCharactersEnd - interGlyphIndex - 1;
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;
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;
237 // if there is next line to retrieve.
238 if(GetNextLine(index, lineIndex, lineRun, lastGlyphOfLine, numberOfLines, isLastLine))
240 UpdateLineInfo(lineRun, currentLineOffset, currentLineHeight, lastGlyphOfLine, isLastLine);
244 if((index == glyphStart) || (isEllipsisEnabled && (((ellipsisPosition == DevelText::EllipsisPosition::MIDDLE) && (index == secondMiddleIndexOfElidedGlyphs)) || ((ellipsisPosition == DevelText::EllipsisPosition::START) && (index - 1 == startIndexOfGlyphs)))))
246 blockPos = currentPosition;
247 blockSize = currentSize;
249 else if((isPrevoiusRightToLeft != isCurrentRightToLeft) || (!Dali::Equals(blockPos.y, currentPosition.y))) //new direction or new line
251 sizesList.PushBack(blockSize);
252 positionsList.PushBack(blockPos);
254 blockPos = currentPosition;
255 blockSize = currentSize;
259 if(isCurrentRightToLeft)
261 blockPos.x -= currentSize.x;
264 blockSize.x += currentSize.x;
267 isPrevoiusRightToLeft = isCurrentRightToLeft;
271 sizesList.PushBack(blockSize);
272 positionsList.PushBack(blockPos);
275 float GetLineLeft(const LineRun& lineRun)
277 return lineRun.alignmentOffset;
280 float GetLineTop(const Vector<LineRun>& lines, const LineRun& lineRun)
283 const int numberOfLines = (int)lines.Count();
285 int currentLineIndex = 0;
286 Vector<LineRun>::ConstIterator endIt = (&lineRun);
287 for(Vector<LineRun>::Iterator it = lines.Begin();
289 ++it, ++currentLineIndex)
292 bool isLastLine = (currentLineIndex + 1) == numberOfLines;
293 lineTop += GetLineHeight(line, isLastLine);
299 float GetLineWidth(const LineRun& lineRun)
301 return lineRun.width;
304 Rect<float> GetLineBoundingRect(ModelPtr textModel, const uint32_t lineIndex)
306 if(textModel->mVisualModel == nullptr)
311 Length numberOfLines = textModel->mVisualModel->GetTotalNumberOfLines();
313 if(lineIndex >= numberOfLines)
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;
323 // Calculate the Left(lineX) = X position.
324 float lineX = GetLineLeft(lineRun) + textModel->mScrollPosition.x;
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;
330 // The rectangle contains the width and height:
331 float lineWidth = GetLineWidth(lineRun);
332 float lineHeight = GetLineHeight(lineRun, isLastLine);
334 return {lineX, lineY, lineWidth, lineHeight};
337 float GetCharacterLeft(const GlyphInfo& glyph, const Vector2& characterPosition)
339 return characterPosition.x - glyph.xBearing;
342 float GetCharacterTop(const float& yPosition)
344 return (-1 * yPosition);
347 float GetCharacterHeight(const GlyphInfo& glyph)
352 float GetCharacterWidth(const GlyphInfo& glyph)
354 return glyph.advance;
357 Rect<> GetCharacterBoundingRect(ModelPtr textModel, const uint32_t charIndex)
359 if(textModel->mVisualModel == nullptr)
364 VisualModelPtr& visualModel = textModel->mVisualModel;
365 LogicalModelPtr& logicalModel = textModel->mLogicalModel;
367 if(charIndex >= logicalModel->mText.Count() || visualModel->mLines.Empty())
372 const Vector<Vector2>& glyphPositions = visualModel->mGlyphPositions;
373 const Vector<GlyphInfo>& glyphs = visualModel->mGlyphs;
374 const Vector<LineRun>& lines = visualModel->mLines;
376 //For each character, the index of the first glyph.
377 const GlyphIndex glyphIndex = visualModel->mCharactersToGlyph[charIndex]; //took its glyphs
379 const Vector2& characterPosition = glyphPositions[glyphIndex];
380 const GlyphInfo& glyphInfo = glyphs[glyphIndex];
382 // GetLineOfCharacter function returns 0 if the lines are empty
383 const int lineIndex = visualModel->GetLineOfCharacter(charIndex);
384 const LineRun& lineRun = lines[lineIndex];
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;
389 //Calculate the Top(characterY): position.Y + previouse lines height + mScrollPosition.y.
390 bool isFirstLine = lineIndex == 0;
392 //If the line is the first line of the text; its top = 0.
393 float lineY = (isFirstLine ? 0 : GetLineTop(lines, lineRun));
395 float characterY = lineY + GetCharacterTop(characterPosition.y) + textModel->mScrollPosition.y;
397 //The rectangle contains the width and height:
398 float characterWidth = GetCharacterWidth(glyphInfo);
399 float characterHeight = GetCharacterHeight(glyphInfo);
401 return {characterX, characterY, characterWidth, characterHeight};
404 int GetCharIndexAtPosition(ModelPtr textModel, float visualX, float visualY)
406 if(textModel == nullptr)
411 VisualModelPtr& visualModel = textModel->mVisualModel;
413 const int totalNumberOfGlyphs = visualModel->mGlyphs.Count();
414 const int totalNumberOfLines = visualModel->mLines.Count();
416 if((0 == totalNumberOfGlyphs) ||
417 (0 == totalNumberOfLines))
422 //The top point of the view = 0.
428 const Vector<LineRun>& lines = visualModel->mLines;
432 int high = totalNumberOfLines;
436 // Searching for the correct line.
437 while(high - low > 1)
439 guess = (high + low) / 2;
440 Vector<LineRun>::ConstIterator it = lines.Begin() + guess;
442 lineTop = GetLineTop(lines, *it);
444 if(lineTop > visualY)
463 bool isLastLine = lineIndex + 1 == totalNumberOfLines;
467 const LineRun& line = *(visualModel->mLines.Begin() + lineIndex);
468 float lineHeight = GetLineHeight(line, isLastLine);
470 // If the visualY is placed after the last line.
471 if(visualY > lineTop + lineHeight)
477 // Start searching for the visualX
478 const LineRun& line = *(visualModel->mLines.Begin() + lineIndex);
480 visualX -= line.alignmentOffset;
482 // Positions of the glyphs
483 const Vector2* const positionsBuffer = visualModel->mGlyphPositions.Begin();
485 const CharacterIndex startCharacter = line.characterRun.characterIndex;
486 const CharacterIndex endCharacter = line.characterRun.characterIndex + line.characterRun.numberOfCharacters - 1;
488 CharacterIndex characterIndexAtPosition = -1;
489 CharacterIndex characterIndex = startCharacter;
490 float characterPosition;
491 float rightMostCharacterPosition;
493 for(; characterIndex != endCharacter; characterIndex++)
495 characterPosition = positionsBuffer[characterIndex].x;
496 rightMostCharacterPosition = positionsBuffer[characterIndex+1].x;
498 if(visualX < rightMostCharacterPosition && visualX >= characterPosition)
500 characterIndexAtPosition = characterIndex;
505 if(characterIndex == endCharacter)
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]);
510 if(visualX >= positionsBuffer[endCharacter].x && visualX < rightMostCharacterPosition)
512 characterIndexAtPosition = endCharacter;
516 return characterIndexAtPosition;
520 } // namespace Toolkit