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-view.h>
22 #include <dali/devel-api/text-abstraction/font-client.h>
23 #include <dali/public-api/math/vector2.h>
34 VisualModelPtr mVisualModel;
35 TextAbstraction::FontClient mFontClient; ///< Handle to the font client.
41 mImpl = new View::Impl();
43 mImpl->mFontClient = TextAbstraction::FontClient::Get();
51 void View::SetVisualModel(VisualModelPtr visualModel)
53 mImpl->mVisualModel = visualModel;
56 const Vector2& View::GetControlSize() const
58 if(mImpl->mVisualModel)
60 return mImpl->mVisualModel->mControlSize;
66 const Vector2& View::GetLayoutSize() const
68 if(mImpl->mVisualModel)
70 return mImpl->mVisualModel->GetLayoutSize();
76 Length View::GetNumberOfGlyphs() const
78 if(mImpl->mVisualModel)
80 const VisualModel& model = *mImpl->mVisualModel;
82 const Length glyphCount = model.mGlyphs.Count();
83 const Length positionCount = model.mGlyphPositions.Count();
85 DALI_ASSERT_DEBUG(positionCount <= glyphCount && "Invalid glyph positions in Model");
87 return (positionCount < glyphCount) ? positionCount : glyphCount;
93 Length View::GetGlyphs(GlyphInfo* glyphs,
94 Vector2* glyphPositions,
96 GlyphIndex glyphIndex,
97 Length numberOfGlyphs) const
99 Length numberOfLaidOutGlyphs = 0u;
100 Length numberOfActualLaidOutGlyphs = 0u;
102 if(mImpl->mVisualModel)
104 bool textElided = false;
105 DevelText::EllipsisPosition::Type ellipsisPosition = GetEllipsisPosition();
107 //Reset indices of ElidedGlyphs
108 mImpl->mVisualModel->SetStartIndexOfElidedGlyphs(0u);
109 mImpl->mVisualModel->SetEndIndexOfElidedGlyphs(numberOfGlyphs);
110 mImpl->mVisualModel->SetFirstMiddleIndexOfElidedGlyphs(0u);
111 mImpl->mVisualModel->SetSecondMiddleIndexOfElidedGlyphs(0u);
113 // If ellipsis is enabled, the number of glyphs the layout engine has laid out may be less than 'numberOfGlyphs'.
114 // Check the last laid out line to know if the layout engine elided some text.
116 const Length numberOfLines = mImpl->mVisualModel->mLines.Count();
117 if(numberOfLines > 0u)
119 const LineRun* const lines = mImpl->mVisualModel->mLines.Begin();
121 //Get line of ellipsis
122 const LineRun* ellipsisLine = nullptr;
123 const LineRun* ellipsisNextLine = nullptr;
124 bool hasEllipsis = false;
125 for(Length lineIndex = 0; lineIndex < numberOfLines; lineIndex++)
127 const LineRun* line = (lines + lineIndex);
132 if(lineIndex < numberOfLines - 1u)
134 ellipsisNextLine = (lines + lineIndex + 1u);
140 // If ellipsis is enabled, calculate the number of laid out glyphs.
141 // Otherwise use the given number of glyphs.
145 numberOfLaidOutGlyphs = numberOfGlyphs;
147 if(ellipsisPosition == DevelText::EllipsisPosition::START)
149 numberOfActualLaidOutGlyphs = numberOfGlyphs - ellipsisLine->glyphRun.glyphIndex;
151 else if(ellipsisPosition == DevelText::EllipsisPosition::MIDDLE)
153 numberOfActualLaidOutGlyphs = 0u;
154 for(Length lineIndex = 0u; lineIndex < numberOfLines; lineIndex++)
156 numberOfActualLaidOutGlyphs += lines[lineIndex].glyphRun.numberOfGlyphs + lines[lineIndex].glyphRunSecondHalf.numberOfGlyphs;
159 else // DevelText::EllipsisPosition::END
161 numberOfActualLaidOutGlyphs = ellipsisLine->glyphRun.glyphIndex + ellipsisLine->glyphRun.numberOfGlyphs;
166 numberOfActualLaidOutGlyphs = numberOfLaidOutGlyphs = numberOfGlyphs;
169 if(0u < numberOfActualLaidOutGlyphs)
171 // Retrieve from the visual model the glyphs and positions.
172 mImpl->mVisualModel->GetGlyphs(glyphs,
174 numberOfLaidOutGlyphs);
176 mImpl->mVisualModel->GetGlyphPositions(glyphPositions,
178 numberOfLaidOutGlyphs);
180 // Get the lines for the given range of glyphs.
181 // The lines contain the alignment offset which needs to be added to the glyph's position.
182 LineIndex firstLineIndex = 0u;
183 Length numberOfLines = 0u;
184 mImpl->mVisualModel->GetNumberOfLines(glyphIndex,
185 numberOfLaidOutGlyphs,
189 Vector<LineRun> lines;
190 lines.Resize(numberOfLines);
191 LineRun* lineBuffer = lines.Begin();
193 mImpl->mVisualModel->GetLinesOfGlyphRange(lineBuffer,
195 numberOfLaidOutGlyphs);
197 // Get the first line for the given glyph range.
198 LineIndex lineIndex = firstLineIndex;
199 LineRun* line = lineBuffer + lineIndex;
201 // Index of the last glyph of the line.
202 GlyphIndex lastGlyphIndexOfLine = (line->isSplitToTwoHalves ? line->glyphRunSecondHalf.glyphIndex + line->glyphRunSecondHalf.numberOfGlyphs : line->glyphRun.glyphIndex + line->glyphRun.numberOfGlyphs) - 1u;
204 // Add the alignment offset to the glyph's position.
206 minLineOffset = line->alignmentOffset;
207 float penY = line->ascender;
208 for(Length index = 0u; index < numberOfLaidOutGlyphs; ++index)
210 Vector2& position = *(glyphPositions + index);
211 position.x += line->alignmentOffset;
214 if(lastGlyphIndexOfLine == index)
216 penY += -line->descender + line->lineSpacing;
218 // Get the next line.
221 if(lineIndex < numberOfLines)
223 line = lineBuffer + lineIndex;
224 minLineOffset = std::min(minLineOffset, line->alignmentOffset);
226 lastGlyphIndexOfLine = (line->isSplitToTwoHalves ? line->glyphRunSecondHalf.glyphIndex + line->glyphRunSecondHalf.numberOfGlyphs : line->glyphRun.glyphIndex + line->glyphRun.numberOfGlyphs) - 1u;
228 penY += line->ascender;
233 // Set index where to set Ellipsis according to the selected position of Ellipsis.
234 // Start with this index to replace its glyph by Ellipsis, if the width is not enough, then remove more glyphs.
235 GlyphIndex startIndexOfEllipsis = 0u;
238 if(ellipsisPosition == DevelText::EllipsisPosition::START)
240 // It's the fisrt glyph in line.
241 startIndexOfEllipsis = ellipsisLine->glyphRun.glyphIndex;
243 else if(ellipsisPosition == DevelText::EllipsisPosition::MIDDLE)
245 // It's the second middle of the line in case the line split to two halves.
246 // Otherwise it's It's the last glyph in line (line before all removed lines).
247 startIndexOfEllipsis = ellipsisLine->isSplitToTwoHalves ? (ellipsisLine->glyphRunSecondHalf.glyphIndex) : (ellipsisLine->glyphRun.glyphIndex + ellipsisLine->glyphRun.numberOfGlyphs - 1u);
249 else // DevelText::EllipsisPosition::END
251 // It's the last glyph in line.
252 startIndexOfEllipsis = ellipsisLine->glyphRun.glyphIndex + ellipsisLine->glyphRun.numberOfGlyphs - 1u;
256 if(1u == numberOfLaidOutGlyphs)
258 // not a point try to do ellipsis with only one laid out character.
260 return numberOfLaidOutGlyphs;
265 const LineRun& elidedLine = *ellipsisLine;
267 if((1u == numberOfLines) &&
268 (GetLineHeight(elidedLine) > mImpl->mVisualModel->mControlSize.height))
270 // Replace the first glyph with ellipsis glyph
271 auto indexOfFirstGlyph = (ellipsisPosition == DevelText::EllipsisPosition::START) ? startIndexOfEllipsis : 0u;
273 // Regardless where the location of ellipsis,in-case the hight of line is greater than control's height
274 // then replace the first glyph with ellipsis glyph.
276 // Get the first glyph which is going to be replaced and the ellipsis glyph.
277 GlyphInfo& glyphInfo = *(glyphs + indexOfFirstGlyph);
278 const GlyphInfo& ellipsisGlyph = mImpl->mFontClient.GetEllipsisGlyph(mImpl->mFontClient.GetPointSize(glyphInfo.fontId));
280 // Change the 'x' and 'y' position of the ellipsis glyph.
281 Vector2& position = *(glyphPositions + indexOfFirstGlyph);
282 position.x = ellipsisGlyph.xBearing;
283 position.y = mImpl->mVisualModel->mControlSize.height - ellipsisGlyph.yBearing;
285 // Replace the glyph by the ellipsis glyph.
286 glyphInfo = ellipsisGlyph;
288 mImpl->mVisualModel->SetStartIndexOfElidedGlyphs(indexOfFirstGlyph);
289 mImpl->mVisualModel->SetEndIndexOfElidedGlyphs(indexOfFirstGlyph);
290 mImpl->mVisualModel->SetFirstMiddleIndexOfElidedGlyphs(indexOfFirstGlyph);
291 mImpl->mVisualModel->SetSecondMiddleIndexOfElidedGlyphs(indexOfFirstGlyph);
293 numberOfLaidOutGlyphs = 1u;
295 return numberOfLaidOutGlyphs;
298 // firstPenX, penY and firstPenSet are used to position the ellipsis glyph if needed.
299 float firstPenX = 0.f; // Used if rtl text is elided.
301 bool firstPenSet = false;
303 // Add the ellipsis glyph.
304 bool inserted = false;
305 float removedGlypsWidth = 0.f;
306 Length numberOfRemovedGlyphs = 0u;
307 GlyphIndex indexOfEllipsis = startIndexOfEllipsis;
309 // Tail Mode: start by the end of line.
310 const bool isTailMode = ellipsisPosition == DevelText::EllipsisPosition::END ||
311 (ellipsisPosition == DevelText::EllipsisPosition::MIDDLE && numberOfLines != 1u);
313 // The ellipsis glyph has to fit in the place where the last glyph(s) is(are) removed.
316 const GlyphInfo& glyphToRemove = *(glyphs + indexOfEllipsis);
318 if(0u != glyphToRemove.fontId)
320 // i.e. The font id of the glyph shaped from the '\n' character is zero.
322 // Need to reshape the glyph as the font may be different in size.
323 const GlyphInfo& ellipsisGlyph = mImpl->mFontClient.GetEllipsisGlyph(mImpl->mFontClient.GetPointSize(glyphToRemove.fontId));
327 const Vector2& position = *(glyphPositions + indexOfEllipsis);
329 // Calculates the penY of the current line. It will be used to position the ellipsis glyph.
330 penY = position.y + glyphToRemove.yBearing;
332 // Calculates the first penX which will be used if rtl text is elided.
333 firstPenX = position.x - glyphToRemove.xBearing;
334 if(firstPenX < -ellipsisGlyph.xBearing)
336 // Avoids to exceed the bounding box when rtl text is elided.
337 firstPenX = -ellipsisGlyph.xBearing;
340 removedGlypsWidth = -ellipsisGlyph.xBearing;
345 removedGlypsWidth += std::min(glyphToRemove.advance, (glyphToRemove.xBearing + glyphToRemove.width));
347 // Calculate the width of the ellipsis glyph and check if it fits.
348 const float ellipsisGlyphWidth = ellipsisGlyph.width + ellipsisGlyph.xBearing;
349 if((ellipsisGlyphWidth < removedGlypsWidth) || (isTailMode ? (indexOfEllipsis == 0u) : (indexOfEllipsis == numberOfGlyphs - 1u)))
351 GlyphInfo& glyphInfo = *(glyphs + indexOfEllipsis);
352 Vector2& position = *(glyphPositions + indexOfEllipsis);
353 position.x -= (0.f > glyphInfo.xBearing) ? glyphInfo.xBearing : 0.f;
355 // Replace the glyph by the ellipsis glyph.
356 glyphInfo = ellipsisGlyph;
358 // Change the 'x' and 'y' position of the ellipsis glyph.
360 if(position.x > firstPenX)
362 position.x = firstPenX + removedGlypsWidth - ellipsisGlyphWidth;
365 position.x += ellipsisGlyph.xBearing;
366 position.y = penY - ellipsisGlyph.yBearing;
374 if(isTailMode && indexOfEllipsis > 0u)
376 // Tail Mode: remove glyphs from startIndexOfEllipsis then decrement indexOfEllipsis, until arrive to index zero.
379 else if(!isTailMode && indexOfEllipsis < numberOfLaidOutGlyphs - 1u)
381 // Not Tail Mode: remove glyphs from startIndexOfEllipsis then increase indexOfEllipsis, until arrive to last index (numberOfGlyphs - 1u).
386 // No space for the ellipsis.
389 ++numberOfRemovedGlyphs;
393 // 'Removes' all the glyphs after the ellipsis glyph.
394 if(ellipsisPosition == DevelText::EllipsisPosition::MIDDLE)
396 //Reduce size, shift glyphs and start from ellipsis glyph
397 numberOfLaidOutGlyphs = numberOfActualLaidOutGlyphs - numberOfRemovedGlyphs;
399 GlyphIndex firstMiddleIndexOfElidedGlyphs = 0u;
400 GlyphIndex secondMiddleIndexOfElidedGlyphs = 0u;
402 bool isOnlySecondHalf = false;
405 // Multi-lines case with MIDDLE
406 // In case the Ellipsis in the end of line,
407 // then this index will be the firstMiddleIndex.
408 // The secondMiddleIndex will be the fisrt index in next line.
409 // But in case there is no line after Ellipsis's line then secondMiddleIndex and endIndex equal firstMiddle
411 // A: are laid out glyphs in line has Ellipsis in the end.
412 // N: are laid out glyphs in lines after removed lines.
413 // R: are removed glyphs.
414 // L: are removed glyphs when removed lines.
415 // AAAAAAAAAAAA...RRR => Here's the firstMiddleIndex (First index after last A)
418 // NNNNNNNNNNNNNN => Here's the secondMiddleIndex (First N)
421 firstMiddleIndexOfElidedGlyphs = indexOfEllipsis;
422 if(ellipsisNextLine != nullptr)
424 secondMiddleIndexOfElidedGlyphs = ellipsisNextLine->glyphRun.glyphIndex;
428 secondMiddleIndexOfElidedGlyphs = firstMiddleIndexOfElidedGlyphs;
429 mImpl->mVisualModel->SetEndIndexOfElidedGlyphs(firstMiddleIndexOfElidedGlyphs);
434 // Single line case with MIDDLE
435 // In case the Ellipsis in the middle of line,
436 // Then the last index in first half will be firstMiddleIndex.
437 // And the indexOfEllipsis will be secondMiddleIndex, which is the first index in second half.
439 // A: are laid out glyphs in first half of line.
440 // N: are laid out glyphs in second half of line.
441 // R: are removed glyphs.
442 // L: re removed glyphs when layouting text
443 // AAAAAAALLLLLLLLLLLRRR...NNNNN
444 // firstMiddleIndex (index of last A)
445 // secondMiddleIndex (index before first N)
447 firstMiddleIndexOfElidedGlyphs = (ellipsisLine->glyphRun.numberOfGlyphs > 0u) ? (ellipsisLine->glyphRun.glyphIndex + ellipsisLine->glyphRun.numberOfGlyphs - 1u) : (ellipsisLine->glyphRun.glyphIndex);
448 secondMiddleIndexOfElidedGlyphs = indexOfEllipsis;
449 isOnlySecondHalf = ellipsisLine->glyphRun.numberOfGlyphs == 0u && ellipsisLine->glyphRunSecondHalf.numberOfGlyphs > 0u;
452 mImpl->mVisualModel->SetFirstMiddleIndexOfElidedGlyphs(firstMiddleIndexOfElidedGlyphs);
453 mImpl->mVisualModel->SetSecondMiddleIndexOfElidedGlyphs(secondMiddleIndexOfElidedGlyphs);
455 // The number of shifted glyphs and shifting positions will be different according to Single-line or Multi-lines.
456 // isOnlySecondHalf will be true when MIDDLE Ellipsis glyph in single line.
459 Length numberOfSecondHalfGlyphs = numberOfLaidOutGlyphs - firstMiddleIndexOfElidedGlyphs;
461 //Copy elided glyphs after the ellipsis glyph.
462 memcpy(glyphs + firstMiddleIndexOfElidedGlyphs, glyphs + secondMiddleIndexOfElidedGlyphs, numberOfSecondHalfGlyphs * sizeof(GlyphInfo));
463 memcpy(glyphPositions + firstMiddleIndexOfElidedGlyphs, glyphPositions + secondMiddleIndexOfElidedGlyphs, numberOfSecondHalfGlyphs * sizeof(Vector2));
467 Length numberOfSecondHalfGlyphs = numberOfLaidOutGlyphs - firstMiddleIndexOfElidedGlyphs + 1u;
469 //Copy elided glyphs after the ellipsis glyph.
470 memcpy(glyphs + firstMiddleIndexOfElidedGlyphs + 1u, glyphs + secondMiddleIndexOfElidedGlyphs, numberOfSecondHalfGlyphs * sizeof(GlyphInfo));
471 memcpy(glyphPositions + firstMiddleIndexOfElidedGlyphs + 1u, glyphPositions + secondMiddleIndexOfElidedGlyphs, numberOfSecondHalfGlyphs * sizeof(Vector2));
474 else if(ellipsisPosition == DevelText::EllipsisPosition::START)
476 numberOfLaidOutGlyphs = numberOfActualLaidOutGlyphs - numberOfRemovedGlyphs;
477 //Copy elided glyphs after the ellipsis glyph.
478 memcpy(glyphs, glyphs + startIndexOfEllipsis + numberOfRemovedGlyphs, numberOfLaidOutGlyphs * sizeof(GlyphInfo));
479 memcpy(glyphPositions, glyphPositions + startIndexOfEllipsis + numberOfRemovedGlyphs, numberOfLaidOutGlyphs * sizeof(Vector2));
480 mImpl->mVisualModel->SetStartIndexOfElidedGlyphs(indexOfEllipsis);
482 else // DevelText::EllipsisPosition::END
484 numberOfLaidOutGlyphs = numberOfActualLaidOutGlyphs - numberOfRemovedGlyphs;
485 mImpl->mVisualModel->SetEndIndexOfElidedGlyphs(indexOfEllipsis);
492 return numberOfLaidOutGlyphs;
495 const Vector4* const View::GetColors() const
497 if(mImpl->mVisualModel)
499 return mImpl->mVisualModel->mColors.Begin();
505 const ColorIndex* const View::GetColorIndices() const
507 if(mImpl->mVisualModel)
509 return mImpl->mVisualModel->mColorIndices.Begin();
515 const Vector4* const View::GetBackgroundColors() const
517 if(mImpl->mVisualModel)
519 return mImpl->mVisualModel->mBackgroundColors.Begin();
525 const ColorIndex* const View::GetBackgroundColorIndices() const
527 if(mImpl->mVisualModel)
529 return mImpl->mVisualModel->mBackgroundColorIndices.Begin();
535 bool const View::IsMarkupBackgroundColorSet() const
537 if(mImpl->mVisualModel)
539 return (mImpl->mVisualModel->mBackgroundColors.Count() > 0);
545 const Vector4& View::GetTextColor() const
547 if(mImpl->mVisualModel)
549 return mImpl->mVisualModel->GetTextColor();
551 return Vector4::ZERO;
554 const Vector2& View::GetShadowOffset() const
556 if(mImpl->mVisualModel)
558 return mImpl->mVisualModel->GetShadowOffset();
560 return Vector2::ZERO;
563 const Vector4& View::GetShadowColor() const
565 if(mImpl->mVisualModel)
567 return mImpl->mVisualModel->GetShadowColor();
569 return Vector4::ZERO;
572 const Vector4& View::GetUnderlineColor() const
574 if(mImpl->mVisualModel)
576 return mImpl->mVisualModel->GetUnderlineColor();
578 return Vector4::ZERO;
581 bool View::IsUnderlineEnabled() const
583 if(mImpl->mVisualModel)
585 return mImpl->mVisualModel->IsUnderlineEnabled();
590 const GlyphInfo* View::GetHyphens() const
592 if(mImpl->mVisualModel)
594 return mImpl->mVisualModel->mHyphen.glyph.Begin();
600 const Length* View::GetHyphenIndices() const
602 if(mImpl->mVisualModel)
604 return mImpl->mVisualModel->mHyphen.index.Begin();
610 Length View::GetHyphensCount() const
612 if(mImpl->mVisualModel)
614 return mImpl->mVisualModel->mHyphen.glyph.Size();
619 float View::GetUnderlineHeight() const
621 if(mImpl->mVisualModel)
623 return mImpl->mVisualModel->GetUnderlineHeight();
628 Length View::GetNumberOfUnderlineRuns() const
630 if(mImpl->mVisualModel)
632 return mImpl->mVisualModel->GetNumberOfUnderlineRuns();
638 void View::GetUnderlineRuns(GlyphRun* underlineRuns,
639 UnderlineRunIndex index,
640 Length numberOfRuns) const
642 if(mImpl->mVisualModel)
644 mImpl->mVisualModel->GetUnderlineRuns(underlineRuns,
650 const Vector4& View::GetOutlineColor() const
652 if(mImpl->mVisualModel)
654 return mImpl->mVisualModel->GetOutlineColor();
656 return Vector4::ZERO;
659 uint16_t View::GetOutlineWidth() const
661 if(mImpl->mVisualModel)
663 return mImpl->mVisualModel->GetOutlineWidth();
668 DevelText::EllipsisPosition::Type View::GetEllipsisPosition() const
670 DevelText::EllipsisPosition::Type ellipsisPosition = DevelText::EllipsisPosition::END;
671 if(mImpl->mVisualModel)
673 const VisualModel& model = *mImpl->mVisualModel;
674 ellipsisPosition = model.GetEllipsisPosition();
677 return ellipsisPosition;
680 bool View::IsTextElideEnabled() const
682 bool isTextElideEnabled = false;
684 if(mImpl->mVisualModel)
686 const VisualModel& model = *mImpl->mVisualModel;
687 isTextElideEnabled = model.IsTextElideEnabled();
690 return isTextElideEnabled;
693 GlyphIndex View::GetStartIndexOfElidedGlyphs() const
695 GlyphIndex startIndexOfElidedGlyphs = 0u;
697 if(mImpl->mVisualModel)
699 const VisualModel& model = *mImpl->mVisualModel;
700 startIndexOfElidedGlyphs = model.GetStartIndexOfElidedGlyphs();
703 return startIndexOfElidedGlyphs;
706 GlyphIndex View::GetEndIndexOfElidedGlyphs() const
708 GlyphIndex endIndexOfElidedGlyphs = 0u;
710 if(mImpl->mVisualModel)
712 const VisualModel& model = *mImpl->mVisualModel;
713 endIndexOfElidedGlyphs = model.GetEndIndexOfElidedGlyphs();
716 return endIndexOfElidedGlyphs;
719 GlyphIndex View::GetFirstMiddleIndexOfElidedGlyphs() const
721 GlyphIndex firstMiddleIndexOfElidedGlyphs = 0u;
723 if(mImpl->mVisualModel)
725 const VisualModel& model = *mImpl->mVisualModel;
726 firstMiddleIndexOfElidedGlyphs = model.GetFirstMiddleIndexOfElidedGlyphs();
729 return firstMiddleIndexOfElidedGlyphs;
732 GlyphIndex View::GetSecondMiddleIndexOfElidedGlyphs() const
734 GlyphIndex secondMiddleIndexOfElidedGlyphs = 0u;
736 if(mImpl->mVisualModel)
738 const VisualModel& model = *mImpl->mVisualModel;
739 secondMiddleIndexOfElidedGlyphs = model.GetSecondMiddleIndexOfElidedGlyphs();
742 return secondMiddleIndexOfElidedGlyphs;
747 } // namespace Toolkit