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>
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>
37 bool GetNextLine(GlyphIndex index, LineIndex& lineIndex, LineRun*& lineRun, GlyphIndex& lastGlyphOfLine, Length numberOfLines, bool& isLastLine)
39 if(index == lastGlyphOfLine)
42 if(lineIndex < numberOfLines)
44 isLastLine = (lineIndex + 1 == numberOfLines);
53 void UpdateLineInfo(const LineRun* lineRun, float& currentLineOffset, float& currentLineHeight, GlyphIndex& lastGlyphOfLine, bool isLastLine)
55 lastGlyphOfLine = lineRun->glyphRun.glyphIndex + lineRun->glyphRun.numberOfGlyphs - 1u;
56 currentLineOffset = currentLineOffset + currentLineHeight;
57 currentLineHeight = GetLineHeight(*lineRun, isLastLine);
60 void GetTextGeometry(ModelPtr textModel, CharacterIndex startIndex, CharacterIndex endIndex, Vector<Vector2>& sizesList, Vector<Vector2>& positionsList)
62 VisualModelPtr& visualModel = textModel->mVisualModel;
63 LogicalModelPtr& logicalModel = textModel->mLogicalModel;
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;
75 positionsList.Clear();
77 if(charactersToGlyphBuffer == nullptr || glyphsPerCharacterBuffer == nullptr || charactersPerGlyphBuffer == nullptr || glyphToCharacterBuffer == nullptr )
82 if(startIndex >= logicalModel->mText.Count() && endIndex >= logicalModel->mText.Count())
87 if(startIndex >= logicalModel->mText.Count())
89 startIndex = static_cast<CharacterIndex>(logicalModel->mText.Count() - 1u);
92 if(endIndex >= logicalModel->mText.Count())
94 endIndex = static_cast<CharacterIndex>(logicalModel->mText.Count() - 1u);
97 if(startIndex > endIndex)
99 std::swap(startIndex, endIndex);
102 LineRun* lineRun = visualModel->mLines.Begin();
103 GlyphIndex glyphStart = *(charactersToGlyphBuffer + startIndex);
105 //if glyph not in the first line (in some ellipsis cases)
106 if(glyphStart < lineRun->glyphRun.glyphIndex)
108 glyphStart = lineRun->glyphRun.glyphIndex;
109 startIndex = *(glyphToCharacterBuffer + glyphStart);
111 if(startIndex > endIndex)
113 std::swap(startIndex, endIndex);
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;
123 LineIndex firstLineIndex = lineIndex;
125 Vector2 textInLinePosition;
127 lineRun += firstLineIndex;
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;
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);
138 bool splitStartGlyph = (numberOfCharactersStart > 1u) && HasLigatureMustBreak(logicalModel->GetScript(startIndex));
139 bool splitEndGlyph = (glyphStart != glyphEnd) && (numberOfCharactersEnd > 1u) && HasLigatureMustBreak(logicalModel->GetScript(endIndex));
142 Vector2 currentPosition;
145 CharacterDirection isCurrentRightToLeft;
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();
155 for(GlyphIndex index = glyphStart; index <= glyphEnd; ++index)
157 if(isEllipsisEnabled)
159 if(ellipsisPosition == DevelText::EllipsisPosition::MIDDLE)
161 if(index >= firstMiddleIndexOfElidedGlyphs &&
162 index < secondMiddleIndexOfElidedGlyphs)
164 if((index - 1 == firstMiddleIndexOfElidedGlyphs) && (firstMiddleIndexOfElidedGlyphs != 0))
166 sizesList.PushBack(blockSize);
167 positionsList.PushBack(blockPos);
170 if(GetNextLine(index, lineIndex, lineRun, lastGlyphOfLine, numberOfLines, isLastLine))
172 UpdateLineInfo(lineRun, currentLineOffset, currentLineHeight, lastGlyphOfLine, isLastLine);
174 // Ignore any glyph that was removed
180 if((ellipsisPosition == DevelText::EllipsisPosition::END) && (index > endIndexOfGlyphs))
182 //skip remaining elided glyphs
185 else if((ellipsisPosition == DevelText::EllipsisPosition::START) && (index <= startIndexOfGlyphs))
187 if(GetNextLine(index, lineIndex, lineRun, lastGlyphOfLine, numberOfLines, isLastLine))
189 UpdateLineInfo(lineRun, currentLineOffset, currentLineHeight, lastGlyphOfLine, isLastLine);
197 const GlyphInfo& glyph = *(glyphsBuffer + index);
198 const Vector2& position = *(positionsBuffer + index);
200 // If NULL, means all of the characters is left to right.
201 isCurrentRightToLeft = (nullptr != modelCharacterDirectionsBuffer ? *(modelCharacterDirectionsBuffer + *(glyphToCharacterBuffer + index)) : false);
203 if(splitStartGlyph && (index == glyphStart))
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);
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;
216 else if(splitEndGlyph && (index == glyphEnd))
218 const float glyphAdvance = glyph.advance / static_cast<float>(numberOfCharactersEnd);
219 const CharacterIndex interGlyphIndex = endIndex - *(glyphToCharacterBuffer + glyphEnd);
220 const Length numberOfCharacters = numberOfCharactersEnd - interGlyphIndex - 1;
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;
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;
235 // if there is next line to retrieve.
236 if(GetNextLine(index, lineIndex, lineRun, lastGlyphOfLine, numberOfLines, isLastLine))
238 UpdateLineInfo(lineRun, currentLineOffset, currentLineHeight, lastGlyphOfLine, isLastLine);
242 if((index == glyphStart) || (isEllipsisEnabled && (((ellipsisPosition == DevelText::EllipsisPosition::MIDDLE) && (index == secondMiddleIndexOfElidedGlyphs)) || ((ellipsisPosition == DevelText::EllipsisPosition::START) && (index - 1 == startIndexOfGlyphs)))))
244 blockPos = currentPosition;
245 blockSize = currentSize;
247 else if((isPrevoiusRightToLeft != isCurrentRightToLeft) || (blockPos.y != currentPosition.y)) //new direction or new line
249 sizesList.PushBack(blockSize);
250 positionsList.PushBack(blockPos);
252 blockPos = currentPosition;
253 blockSize = currentSize;
257 if(isCurrentRightToLeft)
259 blockPos.x -= currentSize.x;
262 blockSize.x += currentSize.x;
265 isPrevoiusRightToLeft = isCurrentRightToLeft;
269 sizesList.PushBack(blockSize);
270 positionsList.PushBack(blockPos);
273 float GetLineLeft(const LineRun& lineRun)
275 return lineRun.alignmentOffset;
278 float GetLineTop(const Vector<LineRun>& lines, const LineRun& lineRun)
281 const int numberOfLines = (int)lines.Count();
283 int currentLineIndex = 0;
284 Vector<LineRun>::ConstIterator endIt = (&lineRun);
285 for(Vector<LineRun>::Iterator it = lines.Begin();
287 ++it, ++currentLineIndex)
290 bool isLastLine = (currentLineIndex + 1) == numberOfLines;
291 lineTop += GetLineHeight(line, isLastLine);
297 float GetLineWidth(const LineRun& lineRun)
299 return lineRun.width;
302 Rect<float> GetLineBoundingRect(ModelPtr textModel, const uint32_t lineIndex)
305 if(textModel->mVisualModel == nullptr)
310 Length numberOfLines = textModel->mVisualModel->GetTotalNumberOfLines();
312 if(lineIndex >= numberOfLines)
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;
322 // Calculate the Left(lineX) = X position.
323 float lineX = GetLineLeft(lineRun) + textModel->mScrollPosition.x;
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;
329 // The rectangle contains the width and height:
330 float lineWidth = GetLineWidth(lineRun);
331 float lineHeight = GetLineHeight(lineRun, isLastLine);
333 return {lineX, lineY, lineWidth, lineHeight};
338 } // namespace Toolkit