+ lineLayout.penX = tmpLineLayout.penX;
+ lineLayout.previousAdvance = tmpLineLayout.previousAdvance;
+
+ lineLayout.length = tmpLineLayout.length;
+ lineLayout.whiteSpaceLengthEndOfLine = tmpLineLayout.whiteSpaceLengthEndOfLine;
+
+ // Sets the maximum ascender.
+ lineLayout.ascender = std::max(lineLayout.ascender, tmpLineLayout.ascender);
+
+ // Sets the minimum descender.
+ lineLayout.descender = std::min(lineLayout.descender, tmpLineLayout.descender);
+
+ // To handle cases START in ellipsis position when want to shift first glyph to let width fit.
+ if(isShifted)
+ {
+ lineLayout.glyphIndex = tmpLineLayout.glyphIndex;
+ lineLayout.characterIndex = tmpLineLayout.characterIndex;
+ }
+
+ lineLayout.isSplitToTwoHalves = tmpLineLayout.isSplitToTwoHalves;
+ lineLayout.glyphIndexInSecondHalfLine = tmpLineLayout.glyphIndexInSecondHalfLine;
+ lineLayout.characterIndexInSecondHalfLine = tmpLineLayout.characterIndexInSecondHalfLine;
+ lineLayout.numberOfGlyphsInSecondHalfLine = tmpLineLayout.numberOfGlyphsInSecondHalfLine;
+ lineLayout.numberOfCharactersInSecondHalfLine = tmpLineLayout.numberOfCharactersInSecondHalfLine;
+ }
+
+ void LayoutRightToLeft(const Parameters& parameters,
+ const BidirectionalLineInfoRun& bidirectionalLineInfo,
+ float& length,
+ float& whiteSpaceLengthEndOfLine)
+ {
+ // Travers characters in line then draw it form right to left by mapping index using visualToLogicalMap.
+ // When the line is spllited by MIDDLE ellipsis then travers the second half of line "characterRunForSecondHalfLine"
+ // then the first half of line "characterRun",
+ // Otherwise travers whole characters in"characterRun".
+
+ const Character* const textBuffer = parameters.textModel->mLogicalModel->mText.Begin();
+ const Length* const charactersPerGlyphBuffer = parameters.textModel->mVisualModel->mCharactersPerGlyph.Begin();
+ const GlyphInfo* const glyphsBuffer = parameters.textModel->mVisualModel->mGlyphs.Begin();
+ const GlyphIndex* const charactersToGlyphsBuffer = parameters.textModel->mVisualModel->mCharactersToGlyph.Begin();
+
+ const float outlineWidth = static_cast<float>(parameters.textModel->GetOutlineWidth());
+ const GlyphIndex lastGlyphOfParagraphPlusOne = parameters.startGlyphIndex + parameters.numberOfGlyphs;
+ const float modelCharacterSpacing = parameters.textModel->mVisualModel->GetCharacterSpacing();
+
+ // Get the character-spacing runs.
+ const Vector<CharacterSpacingGlyphRun>& characterSpacingGlyphRuns = parameters.textModel->mVisualModel->GetCharacterSpacingGlyphRuns();
+
+ CharacterIndex characterLogicalIndex = 0u;
+ CharacterIndex characterVisualIndex = 0u;
+
+ float calculatedAdvance = 0.f;
+
+ // If there are characters in the second half of Line then the first visual index mapped from visualToLogicalMapSecondHalf
+ // Otherwise maps the first visual index from visualToLogicalMap.
+ // This is to initialize the first visual index.
+ if(bidirectionalLineInfo.characterRunForSecondHalfLine.numberOfCharacters > 0u)
+ {
+ characterVisualIndex = bidirectionalLineInfo.characterRunForSecondHalfLine.characterIndex + *(bidirectionalLineInfo.visualToLogicalMapSecondHalf + characterLogicalIndex);
+ }
+ else
+ {
+ characterVisualIndex = bidirectionalLineInfo.characterRun.characterIndex + *(bidirectionalLineInfo.visualToLogicalMap + characterLogicalIndex);
+ }
+
+ bool extendedToSecondHalf = false; // Whether the logical index is extended to second half
+
+ if(RTL == bidirectionalLineInfo.direction)
+ {
+ // If there are characters in the second half of Line.
+ if(bidirectionalLineInfo.characterRunForSecondHalfLine.numberOfCharacters > 0u)
+ {
+ // Keep adding the WhiteSpaces to the whiteSpaceLengthEndOfLine
+ while(TextAbstraction::IsWhiteSpace(*(textBuffer + characterVisualIndex)))
+ {
+ const GlyphInfo& glyphInfo = *(glyphsBuffer + *(charactersToGlyphsBuffer + characterVisualIndex));
+
+ const float characterSpacing = GetGlyphCharacterSpacing(characterVisualIndex, characterSpacingGlyphRuns, modelCharacterSpacing);
+ calculatedAdvance = GetCalculatedAdvance(*(textBuffer + characterVisualIndex), characterSpacing, glyphInfo.advance);
+ whiteSpaceLengthEndOfLine += calculatedAdvance;
+
+ ++characterLogicalIndex;
+ characterVisualIndex = bidirectionalLineInfo.characterRunForSecondHalfLine.characterIndex + *(bidirectionalLineInfo.visualToLogicalMapSecondHalf + characterLogicalIndex);
+ }
+ }
+
+ // If all characters in the second half of Line are WhiteSpaces.
+ // then continue adding the WhiteSpaces from the first hel of Line.
+ // Also this is valid when the line was not splitted.
+ if(characterLogicalIndex == bidirectionalLineInfo.characterRunForSecondHalfLine.numberOfCharacters)
+ {
+ extendedToSecondHalf = true; // Whether the logical index is extended to second half
+ characterLogicalIndex = 0u;
+ characterVisualIndex = bidirectionalLineInfo.characterRun.characterIndex + *(bidirectionalLineInfo.visualToLogicalMap + characterLogicalIndex);
+
+ // Keep adding the WhiteSpaces to the whiteSpaceLengthEndOfLine
+ while(TextAbstraction::IsWhiteSpace(*(textBuffer + characterVisualIndex)))
+ {
+ const GlyphInfo& glyphInfo = *(glyphsBuffer + *(charactersToGlyphsBuffer + characterVisualIndex));
+
+ const float characterSpacing = GetGlyphCharacterSpacing(characterVisualIndex, characterSpacingGlyphRuns, modelCharacterSpacing);
+ calculatedAdvance = GetCalculatedAdvance(*(textBuffer + characterVisualIndex), characterSpacing, glyphInfo.advance);
+ whiteSpaceLengthEndOfLine += calculatedAdvance;
+
+ ++characterLogicalIndex;
+ characterVisualIndex = bidirectionalLineInfo.characterRun.characterIndex + *(bidirectionalLineInfo.visualToLogicalMap + characterLogicalIndex);
+ }
+ }
+ }
+
+ // Here's the first index of character is not WhiteSpace
+ const GlyphIndex glyphIndex = *(charactersToGlyphsBuffer + characterVisualIndex);
+
+ // Check whether the first glyph comes from a character that is shaped in multiple glyphs.
+ const Length numberOfGLyphsInGroup = GetNumberOfGlyphsOfGroup(glyphIndex,
+ lastGlyphOfParagraphPlusOne,
+ charactersPerGlyphBuffer);
+
+ GlyphMetrics glyphMetrics;
+ const float characterSpacing = GetGlyphCharacterSpacing(glyphIndex, characterSpacingGlyphRuns, modelCharacterSpacing);
+ calculatedAdvance = GetCalculatedAdvance(*(textBuffer + characterVisualIndex), characterSpacing, (*(glyphsBuffer + glyphIndex)).advance);
+ GetGlyphsMetrics(glyphIndex,
+ numberOfGLyphsInGroup,
+ glyphMetrics,
+ glyphsBuffer,
+ mMetrics,
+ calculatedAdvance);
+
+ float penX = -glyphMetrics.xBearing + mCursorWidth + outlineWidth;
+
+ // Traverses the characters of the right to left paragraph.
+ // Continue in the second half of line, because in it the first index of character that is not WhiteSpace.
+ if(!extendedToSecondHalf &&
+ bidirectionalLineInfo.characterRunForSecondHalfLine.numberOfCharacters > 0u)
+ {
+ for(; characterLogicalIndex < bidirectionalLineInfo.characterRunForSecondHalfLine.numberOfCharacters;)
+ {
+ // Convert the character in the logical order into the character in the visual order.
+ const CharacterIndex characterVisualIndex = bidirectionalLineInfo.characterRunForSecondHalfLine.characterIndex + *(bidirectionalLineInfo.visualToLogicalMapSecondHalf + characterLogicalIndex);
+ const bool isWhiteSpace = TextAbstraction::IsWhiteSpace(*(textBuffer + characterVisualIndex));
+
+ const GlyphIndex glyphIndex = *(charactersToGlyphsBuffer + characterVisualIndex);
+
+ // Check whether this glyph comes from a character that is shaped in multiple glyphs.
+ const Length numberOfGLyphsInGroup = GetNumberOfGlyphsOfGroup(glyphIndex,
+ lastGlyphOfParagraphPlusOne,
+ charactersPerGlyphBuffer);
+
+ characterLogicalIndex += *(charactersPerGlyphBuffer + glyphIndex + numberOfGLyphsInGroup - 1u);
+
+ GlyphMetrics glyphMetrics;
+ const float characterSpacing = GetGlyphCharacterSpacing(glyphIndex, characterSpacingGlyphRuns, modelCharacterSpacing);
+ calculatedAdvance = GetCalculatedAdvance(*(textBuffer + characterVisualIndex), characterSpacing, (*(glyphsBuffer + glyphIndex)).advance);
+ GetGlyphsMetrics(glyphIndex,
+ numberOfGLyphsInGroup,
+ glyphMetrics,
+ glyphsBuffer,
+ mMetrics,
+ calculatedAdvance);
+
+ if(isWhiteSpace)
+ {
+ // If glyph is WhiteSpace then:
+ // For RTL it is whitespace but not at endOfLine. Use "advance" to accumulate length and shift penX.
+ // the endOfLine in RTL was the headOfLine for layouting.
+ // But for LTR added it to the endOfLine and use "advance" to accumulate length.
+ if(RTL == bidirectionalLineInfo.direction)
+ {
+ length += glyphMetrics.advance;
+ }
+ else
+ {
+ whiteSpaceLengthEndOfLine += glyphMetrics.advance;
+ }
+ penX += glyphMetrics.advance;
+ }
+ else
+ {
+ // If glyph is not whiteSpace then:
+ // Reset endOfLine for LTR because there is a non-whiteSpace so the tail of line is not whiteSpaces
+ // Use "advance" and "interGlyphExtraAdvance" to shift penX.
+ // Set length to the maximum possible length, of the current glyph "xBearing" and "width" are shifted penX to length greater than current lenght.
+ // Otherwise the current length is maximum.
+ if(LTR == bidirectionalLineInfo.direction)
+ {
+ whiteSpaceLengthEndOfLine = 0.f;
+ }
+ length = std::max(length, penX + glyphMetrics.xBearing + glyphMetrics.width);
+ penX += (glyphMetrics.advance + parameters.interGlyphExtraAdvance);
+ }
+ }
+ }
+
+ // Continue traversing in the first half of line or in the whole line.
+ // If the second half of line was extended then continue from logical index in the first half of line
+ // Also this is valid when the line was not splitted and there were WhiteSpace.
+ // Otherwise start from first logical index in line.
+ characterLogicalIndex = extendedToSecondHalf ? characterLogicalIndex : 0u;
+ for(; characterLogicalIndex < bidirectionalLineInfo.characterRun.numberOfCharacters;)
+ {
+ // Convert the character in the logical order into the character in the visual order.
+ const CharacterIndex characterVisualIndex = bidirectionalLineInfo.characterRun.characterIndex + *(bidirectionalLineInfo.visualToLogicalMap + characterLogicalIndex);
+ const bool isWhiteSpace = TextAbstraction::IsWhiteSpace(*(textBuffer + characterVisualIndex));
+
+ const GlyphIndex glyphIndex = *(charactersToGlyphsBuffer + characterVisualIndex);
+
+ // Check whether this glyph comes from a character that is shaped in multiple glyphs.
+ const Length numberOfGLyphsInGroup = GetNumberOfGlyphsOfGroup(glyphIndex,
+ lastGlyphOfParagraphPlusOne,
+ charactersPerGlyphBuffer);
+
+ characterLogicalIndex += *(charactersPerGlyphBuffer + glyphIndex + numberOfGLyphsInGroup - 1u);
+
+ GlyphMetrics glyphMetrics;
+ const float characterSpacing = GetGlyphCharacterSpacing(glyphIndex, characterSpacingGlyphRuns, modelCharacterSpacing);
+ calculatedAdvance = GetCalculatedAdvance(*(textBuffer + characterVisualIndex), characterSpacing, (*(glyphsBuffer + glyphIndex)).advance);
+ GetGlyphsMetrics(glyphIndex,
+ numberOfGLyphsInGroup,
+ glyphMetrics,
+ glyphsBuffer,
+ mMetrics,
+ calculatedAdvance);
+
+ if(isWhiteSpace)
+ {
+ // If glyph is WhiteSpace then:
+ // For RTL it is whitespace but not at endOfLine. Use "advance" to accumulate length and shift penX.
+ // the endOfLine in RTL was the headOfLine for layouting.
+ // But for LTR added it to the endOfLine and use "advance" to accumulate length.
+ if(RTL == bidirectionalLineInfo.direction)
+ {
+ length += glyphMetrics.advance;
+ }
+ else
+ {
+ whiteSpaceLengthEndOfLine += glyphMetrics.advance;
+ }
+ penX += glyphMetrics.advance;
+ }
+ else
+ {
+ // If glyph is not whiteSpace then:
+ // Reset endOfLine for LTR because there is a non-whiteSpace so the tail of line is not whiteSpaces
+ // Use "advance" and "interGlyphExtraAdvance" to shift penX.
+ // Set length to the maximum possible length, of the current glyph "xBearing" and "width" are shifted penX to length greater than current lenght.
+ // Otherwise the current length is maximum.
+ if(LTR == bidirectionalLineInfo.direction)
+ {
+ whiteSpaceLengthEndOfLine = 0.f;
+ }
+ length = std::max(length, penX + glyphMetrics.xBearing + glyphMetrics.width);
+ penX += (glyphMetrics.advance + parameters.interGlyphExtraAdvance);
+ }
+ }
+ }
+
+ void ReorderBiDiLayout(const Parameters& parameters,
+ LayoutBidiParameters& bidiParameters,
+ const LineLayout& currentLineLayout,
+ LineLayout& lineLayout,
+ bool breakInCharacters,
+ bool enforceEllipsisInSingleLine)
+ {
+ const Length* const charactersPerGlyphBuffer = parameters.textModel->mVisualModel->mCharactersPerGlyph.Begin();
+
+ // The last glyph to be laid-out.
+ const GlyphIndex lastGlyphOfParagraphPlusOne = parameters.startGlyphIndex + parameters.numberOfGlyphs;
+
+ const Vector<BidirectionalParagraphInfoRun>& bidirectionalParagraphsInfo = parameters.textModel->mLogicalModel->mBidirectionalParagraphInfo;
+
+ const BidirectionalParagraphInfoRun& bidirectionalParagraphInfo = bidirectionalParagraphsInfo[bidiParameters.bidiParagraphIndex];
+ if((lineLayout.characterIndex >= bidirectionalParagraphInfo.characterRun.characterIndex) &&
+ (lineLayout.characterIndex < bidirectionalParagraphInfo.characterRun.characterIndex + bidirectionalParagraphInfo.characterRun.numberOfCharacters))
+ {
+ Vector<BidirectionalLineInfoRun>& bidirectionalLinesInfo = parameters.textModel->mLogicalModel->mBidirectionalLineInfo;
+
+ // Sets the visual to logical map tables needed to reorder the text.
+ ReorderLine(bidirectionalParagraphInfo,
+ bidirectionalLinesInfo,
+ bidiParameters.bidiLineIndex,
+ lineLayout.characterIndex,
+ lineLayout.numberOfCharacters,
+ lineLayout.characterIndexInSecondHalfLine,
+ lineLayout.numberOfCharactersInSecondHalfLine,
+ bidiParameters.paragraphDirection);
+
+ // Recalculate the length of the line and update the layout.
+ const BidirectionalLineInfoRun& bidirectionalLineInfo = *(bidirectionalLinesInfo.Begin() + bidiParameters.bidiLineIndex);
+
+ if(!bidirectionalLineInfo.isIdentity)
+ {
+ float length = 0.f;
+ float whiteSpaceLengthEndOfLine = 0.f;
+ LayoutRightToLeft(parameters,
+ bidirectionalLineInfo,
+ length,
+ whiteSpaceLengthEndOfLine);
+
+ lineLayout.whiteSpaceLengthEndOfLine = whiteSpaceLengthEndOfLine;
+ if(!Equals(length, lineLayout.length))
+ {
+ const bool isMultiline = (!enforceEllipsisInSingleLine) && (mLayout == MULTI_LINE_BOX);
+
+ if(isMultiline && (length > parameters.boundingBox.width))
+ {
+ if(breakInCharacters || (isMultiline && (0u == currentLineLayout.numberOfGlyphs)))
+ {
+ // The word doesn't fit in one line. It has to be split by character.
+
+ // Remove the last laid out glyph(s) as they doesn't fit.
+ for(GlyphIndex glyphIndex = lineLayout.glyphIndex + lineLayout.numberOfGlyphs - 1u; glyphIndex >= lineLayout.glyphIndex;)
+ {
+ const Length numberOfGLyphsInGroup = GetNumberOfGlyphsOfGroup(glyphIndex,
+ lastGlyphOfParagraphPlusOne,
+ charactersPerGlyphBuffer);
+
+ const Length numberOfCharacters = *(charactersPerGlyphBuffer + glyphIndex + numberOfGLyphsInGroup - 1u);
+
+ lineLayout.numberOfGlyphs -= numberOfGLyphsInGroup;
+ lineLayout.numberOfCharacters -= numberOfCharacters;
+
+ AdjustLayout(parameters,
+ bidiParameters,
+ bidirectionalParagraphInfo,
+ lineLayout);
+
+ if(lineLayout.length < parameters.boundingBox.width)
+ {
+ break;
+ }
+
+ if(glyphIndex < numberOfGLyphsInGroup)
+ {
+ // avoids go under zero for an unsigned int.
+ break;
+ }
+
+ glyphIndex -= numberOfGLyphsInGroup;
+ }
+ }
+ else
+ {
+ lineLayout = currentLineLayout;
+
+ AdjustLayout(parameters,
+ bidiParameters,
+ bidirectionalParagraphInfo,
+ lineLayout);
+ }
+ }
+ else
+ {
+ lineLayout.length = std::max(length, lineLayout.length);
+ }
+ }
+ }
+ }
+ }
+
+ void AdjustLayout(const Parameters& parameters,
+ LayoutBidiParameters& bidiParameters,
+ const BidirectionalParagraphInfoRun& bidirectionalParagraphInfo,
+ LineLayout& lineLayout)
+ {
+ Vector<BidirectionalLineInfoRun>& bidirectionalLinesInfo = parameters.textModel->mLogicalModel->mBidirectionalLineInfo;
+
+ // Remove current reordered line.
+ bidirectionalLinesInfo.Erase(bidirectionalLinesInfo.Begin() + bidiParameters.bidiLineIndex);
+
+ // Re-build the conversion table without the removed glyphs.
+ ReorderLine(bidirectionalParagraphInfo,
+ bidirectionalLinesInfo,
+ bidiParameters.bidiLineIndex,
+ lineLayout.characterIndex,
+ lineLayout.numberOfCharacters,
+ lineLayout.characterIndexInSecondHalfLine,
+ lineLayout.numberOfCharactersInSecondHalfLine,
+ bidiParameters.paragraphDirection);
+
+ const BidirectionalLineInfoRun& bidirectionalLineInfo = *(bidirectionalLinesInfo.Begin() + bidiParameters.bidiLineIndex);
+
+ float length = 0.f;
+ float whiteSpaceLengthEndOfLine = 0.f;
+ LayoutRightToLeft(parameters,
+ bidirectionalLineInfo,
+ length,
+ whiteSpaceLengthEndOfLine);
+
+ lineLayout.length = length;
+ lineLayout.whiteSpaceLengthEndOfLine = whiteSpaceLengthEndOfLine;
+ }
+
+ /**
+ * Retrieves the line layout for a given box width.
+ *
+ * @note This method starts to layout text as if it was left to right. However, it might be differences in the length
+ * of the line if it's a bidirectional one. If the paragraph is bidirectional, this method will call a function
+ * to reorder the line and recalculate its length.
+ *
+
+ * @param[in] parameters The layout parameters.
+ * @param[] bidiParameters Bidirectional info for the current line.
+ * @param[out] lineLayout The line layout.
+ * @param[in] completelyFill Whether to completely fill the line ( even if the last word exceeds the boundaries ).
+ * @param[in] ellipsisPosition Where is the location the text elide
+ */
+ void GetLineLayoutForBox(const Parameters& parameters,
+ LayoutBidiParameters& bidiParameters,
+ LineLayout& lineLayout,
+ bool completelyFill,
+ DevelText::EllipsisPosition::Type ellipsisPosition,
+ bool enforceEllipsisInSingleLine,
+ bool elideTextEnabled)
+ {
+ DALI_LOG_INFO(gLogFilter, Debug::Verbose, "-->GetLineLayoutForBox\n");
+ DALI_LOG_INFO(gLogFilter, Debug::Verbose, " initial glyph index : %d\n", lineLayout.glyphIndex);
+
+ const Character* const textBuffer = parameters.textModel->mLogicalModel->mText.Begin();
+ const Length* const charactersPerGlyphBuffer = parameters.textModel->mVisualModel->mCharactersPerGlyph.Begin();
+ const GlyphInfo* const glyphsBuffer = parameters.textModel->mVisualModel->mGlyphs.Begin();
+ const CharacterIndex* const glyphsToCharactersBuffer = parameters.textModel->mVisualModel->mGlyphsToCharacters.Begin();
+ const LineBreakInfo* const lineBreakInfoBuffer = parameters.textModel->mLogicalModel->mLineBreakInfo.Begin();
+
+ const float outlineWidth = static_cast<float>(parameters.textModel->GetOutlineWidth());
+ const Length totalNumberOfGlyphs = parameters.textModel->mVisualModel->mGlyphs.Count();
+
+ const bool isMultiline = !enforceEllipsisInSingleLine && (mLayout == MULTI_LINE_BOX);
+ const bool isWordLaidOut = parameters.textModel->mLineWrapMode == Text::LineWrap::WORD ||
+ (parameters.textModel->mLineWrapMode == (Text::LineWrap::Mode)DevelText::LineWrap::HYPHENATION) ||
+ (parameters.textModel->mLineWrapMode == (Text::LineWrap::Mode)DevelText::LineWrap::MIXED);
+ const bool isHyphenMode = parameters.textModel->mLineWrapMode == (Text::LineWrap::Mode)DevelText::LineWrap::HYPHENATION;
+ const bool isMixedMode = parameters.textModel->mLineWrapMode == (Text::LineWrap::Mode)DevelText::LineWrap::MIXED;
+
+ const bool isSplitToTwoHalves = elideTextEnabled && !isMultiline && ellipsisPosition == DevelText::EllipsisPosition::MIDDLE;
+
+ // The last glyph to be laid-out.
+ const GlyphIndex lastGlyphOfParagraphPlusOne = parameters.startGlyphIndex + parameters.numberOfGlyphs;
+
+ // If the first glyph has a negative bearing its absolute value needs to be added to the line length.
+ // In the case the line starts with a right to left character, if the width is longer than the advance,
+ // the difference needs to be added to the line length.
+
+ // Check whether the first glyph comes from a character that is shaped in multiple glyphs.
+ const Length numberOfGLyphsInGroup = GetNumberOfGlyphsOfGroup(lineLayout.glyphIndex,
+ lastGlyphOfParagraphPlusOne,
+ charactersPerGlyphBuffer);
+
+ float targetWidth = parameters.boundingBox.width;
+ float widthFirstHalf = ((ellipsisPosition != DevelText::EllipsisPosition::MIDDLE) ? targetWidth : targetWidth - std::floor(targetWidth / 2));
+
+ bool isSecondHalf = false;
+ // Character Spacing
+ const float modelCharacterSpacing = parameters.textModel->mVisualModel->GetCharacterSpacing();
+ float calculatedAdvance = 0.f;
+ Vector<CharacterIndex>& glyphToCharacterMap = parameters.textModel->mVisualModel->mGlyphsToCharacters;
+ const CharacterIndex* glyphToCharacterMapBuffer = glyphToCharacterMap.Begin();
+
+ // Get the character-spacing runs.
+ const Vector<CharacterSpacingGlyphRun>& characterSpacingGlyphRuns = parameters.textModel->mVisualModel->GetCharacterSpacingGlyphRuns();
+
+ GlyphMetrics glyphMetrics;
+ const float characterSpacing = GetGlyphCharacterSpacing(lineLayout.glyphIndex, characterSpacingGlyphRuns, modelCharacterSpacing);
+ calculatedAdvance = GetCalculatedAdvance(*(textBuffer + (*(glyphToCharacterMapBuffer + lineLayout.glyphIndex))), characterSpacing, (*(glyphsBuffer + lineLayout.glyphIndex)).advance);
+ GetGlyphsMetrics(lineLayout.glyphIndex,
+ numberOfGLyphsInGroup,
+ glyphMetrics,
+ glyphsBuffer,
+ mMetrics,
+ calculatedAdvance);
+
+ // Set the direction of the first character of the line.
+ lineLayout.characterIndex = *(glyphsToCharactersBuffer + lineLayout.glyphIndex);
+
+ // Stores temporary line layout which has not been added to the final line layout.
+ LineLayout tmpLineLayout;
+
+ // Initialize the start point.
+
+ // The initial start point is zero. However it needs a correction according the 'x' bearing of the first glyph.
+ // i.e. if the bearing of the first glyph is negative it may exceed the boundaries of the text area.
+ // It needs to add as well space for the cursor if the text is in edit mode and extra space in case the text is outlined.
+ tmpLineLayout.penX = -glyphMetrics.xBearing + mCursorWidth + outlineWidth;
+
+ // Calculate the line height if there is no characters.
+ FontId lastFontId = glyphMetrics.fontId;
+ UpdateLineHeight(glyphMetrics, tmpLineLayout);
+
+ bool oneWordLaidOut = false;
+ bool oneHyphenLaidOut = false;
+ GlyphIndex hyphenIndex = 0;
+ GlyphInfo hyphenGlyph;
+
+ for(GlyphIndex glyphIndex = lineLayout.glyphIndex;
+ glyphIndex < lastGlyphOfParagraphPlusOne;)
+ {
+ DALI_LOG_INFO(gLogFilter, Debug::Verbose, " glyph index : %d\n", glyphIndex);
+
+ // Check whether this glyph comes from a character that is shaped in multiple glyphs.
+ const Length numberOfGLyphsInGroup = GetNumberOfGlyphsOfGroup(glyphIndex,
+ lastGlyphOfParagraphPlusOne,
+ charactersPerGlyphBuffer);
+
+ GlyphMetrics glyphMetrics;
+ const float characterSpacing = GetGlyphCharacterSpacing(glyphIndex, characterSpacingGlyphRuns, modelCharacterSpacing);
+ calculatedAdvance = GetCalculatedAdvance(*(textBuffer + (*(glyphToCharacterMapBuffer + glyphIndex))), characterSpacing, (*(glyphsBuffer + glyphIndex)).advance);
+ GetGlyphsMetrics(glyphIndex,
+ numberOfGLyphsInGroup,
+ glyphMetrics,
+ glyphsBuffer,
+ mMetrics,
+ calculatedAdvance);
+
+ const bool isLastGlyph = glyphIndex + numberOfGLyphsInGroup == totalNumberOfGlyphs;
+
+ // Check if the font of the current glyph is the same of the previous one.
+ // If it's different the ascender and descender need to be updated.
+ if(lastFontId != glyphMetrics.fontId)
+ {
+ UpdateLineHeight(glyphMetrics, tmpLineLayout);
+ lastFontId = glyphMetrics.fontId;
+ }
+
+ // Get the character indices for the current glyph. The last character index is needed
+ // because there are glyphs formed by more than one character but their break info is
+ // given only for the last character.
+ const Length charactersPerGlyph = *(charactersPerGlyphBuffer + glyphIndex + numberOfGLyphsInGroup - 1u);
+ const bool hasCharacters = charactersPerGlyph > 0u;
+ const CharacterIndex characterFirstIndex = *(glyphsToCharactersBuffer + glyphIndex);
+ const CharacterIndex characterLastIndex = characterFirstIndex + (hasCharacters ? charactersPerGlyph - 1u : 0u);
+
+ // Get the line break info for the current character.
+ const LineBreakInfo lineBreakInfo = hasCharacters ? *(lineBreakInfoBuffer + characterLastIndex) : TextAbstraction::LINE_NO_BREAK;
+
+ if(isSecondHalf)
+ {
+ // Increase the number of characters.
+ tmpLineLayout.numberOfCharactersInSecondHalfLine += charactersPerGlyph;
+
+ // Increase the number of glyphs.
+ tmpLineLayout.numberOfGlyphsInSecondHalfLine += numberOfGLyphsInGroup;
+ }
+ else
+ {
+ // Increase the number of characters.
+ tmpLineLayout.numberOfCharacters += charactersPerGlyph;
+
+ // Increase the number of glyphs.
+ tmpLineLayout.numberOfGlyphs += numberOfGLyphsInGroup;
+ }
+
+ // Check whether is a white space.
+ const Character character = *(textBuffer + characterFirstIndex);
+ const bool isWhiteSpace = TextAbstraction::IsWhiteSpace(character);
+
+ // Calculate the length of the line.
+
+ // Used to restore the temporal line layout when a single word does not fit in the control's width and is split by character.
+ const float previousTmpPenX = tmpLineLayout.penX;
+ const float previousTmpAdvance = tmpLineLayout.previousAdvance;
+ const float previousTmpLength = tmpLineLayout.length;
+ const float previousTmpWhiteSpaceLengthEndOfLine = tmpLineLayout.whiteSpaceLengthEndOfLine;
+
+ if(isWhiteSpace)
+ {
+ // Add the length to the length of white spaces at the end of the line.
+ tmpLineLayout.whiteSpaceLengthEndOfLine += glyphMetrics.advance;
+ // The advance is used as the width is always zero for the white spaces.
+ }
+ else
+ {
+ tmpLineLayout.penX += tmpLineLayout.previousAdvance + tmpLineLayout.whiteSpaceLengthEndOfLine;
+ tmpLineLayout.previousAdvance = (glyphMetrics.advance + parameters.interGlyphExtraAdvance);
+
+ tmpLineLayout.length = std::max(tmpLineLayout.length, tmpLineLayout.penX + glyphMetrics.xBearing + glyphMetrics.width);
+
+ // Clear the white space length at the end of the line.
+ tmpLineLayout.whiteSpaceLengthEndOfLine = 0.f;
+ }
+
+ if(isSplitToTwoHalves && (!isSecondHalf) &&
+ (tmpLineLayout.length + tmpLineLayout.whiteSpaceLengthEndOfLine > widthFirstHalf))
+ {
+ tmpLineLayout.numberOfCharacters -= charactersPerGlyph;
+ tmpLineLayout.numberOfGlyphs -= numberOfGLyphsInGroup;
+
+ tmpLineLayout.numberOfCharactersInSecondHalfLine += charactersPerGlyph;
+ tmpLineLayout.numberOfGlyphsInSecondHalfLine += numberOfGLyphsInGroup;
+
+ tmpLineLayout.glyphIndexInSecondHalfLine = tmpLineLayout.glyphIndex + tmpLineLayout.numberOfGlyphs;
+ tmpLineLayout.characterIndexInSecondHalfLine = tmpLineLayout.characterIndex + tmpLineLayout.numberOfCharacters;
+
+ tmpLineLayout.isSplitToTwoHalves = isSecondHalf = true;
+ }
+ // Check if the accumulated length fits in the width of the box.
+ if((ellipsisPosition == DevelText::EllipsisPosition::START ||
+ (ellipsisPosition == DevelText::EllipsisPosition::MIDDLE && isSecondHalf)) &&
+ completelyFill && !isMultiline &&
+ (tmpLineLayout.length + tmpLineLayout.whiteSpaceLengthEndOfLine > targetWidth))
+ {
+ GlyphIndex glyphIndexToRemove = isSecondHalf ? tmpLineLayout.glyphIndexInSecondHalfLine : tmpLineLayout.glyphIndex;
+
+ while(tmpLineLayout.length + tmpLineLayout.whiteSpaceLengthEndOfLine > targetWidth && glyphIndexToRemove < glyphIndex)
+ {
+ GlyphMetrics glyphMetrics;
+ const float characterSpacing = GetGlyphCharacterSpacing(glyphIndexToRemove, characterSpacingGlyphRuns, modelCharacterSpacing);
+ calculatedAdvance = GetCalculatedAdvance(*(textBuffer + (*(glyphToCharacterMapBuffer + glyphIndexToRemove))), characterSpacing, (*(glyphsBuffer + glyphIndexToRemove)).advance);
+ GetGlyphsMetrics(glyphIndexToRemove,
+ numberOfGLyphsInGroup,
+ glyphMetrics,
+ glyphsBuffer,
+ mMetrics,
+ calculatedAdvance);
+
+ const Length numberOfGLyphsInGroup = GetNumberOfGlyphsOfGroup(glyphIndexToRemove,
+ lastGlyphOfParagraphPlusOne,
+ charactersPerGlyphBuffer);
+
+ const Length charactersPerGlyph = *(charactersPerGlyphBuffer + glyphIndexToRemove + numberOfGLyphsInGroup - 1u);
+ const bool hasCharacters = charactersPerGlyph > 0u;
+ const CharacterIndex characterFirstIndex = *(glyphsToCharactersBuffer + glyphIndexToRemove);
+ const CharacterIndex characterLastIndex = characterFirstIndex + (hasCharacters ? charactersPerGlyph - 1u : 0u);
+
+ // Check whether is a white space.
+ const Character character = *(textBuffer + characterFirstIndex);
+ const bool isRemovedGlyphWhiteSpace = TextAbstraction::IsWhiteSpace(character);
+
+ if(isSecondHalf)
+ {
+ // Decrease the number of characters for SecondHalf.
+ tmpLineLayout.numberOfCharactersInSecondHalfLine -= charactersPerGlyph;
+
+ // Decrease the number of glyphs for SecondHalf.
+ tmpLineLayout.numberOfGlyphsInSecondHalfLine -= numberOfGLyphsInGroup;
+ }
+ else
+ {
+ // Decrease the number of characters.
+ tmpLineLayout.numberOfCharacters -= charactersPerGlyph;
+
+ // Decrease the number of glyphs.
+ tmpLineLayout.numberOfGlyphs -= numberOfGLyphsInGroup;
+ }
+
+ if(isRemovedGlyphWhiteSpace)
+ {
+ tmpLineLayout.penX -= glyphMetrics.advance;
+ tmpLineLayout.length -= glyphMetrics.advance;
+ }
+ else
+ {
+ tmpLineLayout.penX -= (glyphMetrics.advance + parameters.interGlyphExtraAdvance);
+ tmpLineLayout.length -= (std::min(glyphMetrics.advance + parameters.interGlyphExtraAdvance, glyphMetrics.xBearing + glyphMetrics.width));
+ }
+
+ if(isSecondHalf)
+ {
+ tmpLineLayout.glyphIndexInSecondHalfLine += numberOfGLyphsInGroup;
+ tmpLineLayout.characterIndexInSecondHalfLine = characterLastIndex + 1u;
+ glyphIndexToRemove = tmpLineLayout.glyphIndexInSecondHalfLine;
+ }
+ else
+ {
+ tmpLineLayout.glyphIndex += numberOfGLyphsInGroup;
+ tmpLineLayout.characterIndex = characterLastIndex + 1u;
+ glyphIndexToRemove = tmpLineLayout.glyphIndex;
+ }
+ }
+ }
+ else if((completelyFill || isMultiline) &&
+ (tmpLineLayout.length > targetWidth))
+ {
+ // Current word does not fit in the box's width.
+ if(((oneHyphenLaidOut && isHyphenMode) ||
+ (!oneWordLaidOut && isMixedMode && oneHyphenLaidOut)) &&
+ !completelyFill)
+ {
+ parameters.textModel->mVisualModel->mHyphen.glyph.PushBack(hyphenGlyph);
+ parameters.textModel->mVisualModel->mHyphen.index.PushBack(hyphenIndex + 1);
+ }
+
+ if((!oneWordLaidOut && !oneHyphenLaidOut) || completelyFill)
+ {
+ DALI_LOG_INFO(gLogFilter, Debug::Verbose, " Break the word by character\n");
+
+ // The word doesn't fit in the control's width. It needs to be split by character.
+ if(tmpLineLayout.numberOfGlyphs + tmpLineLayout.numberOfGlyphsInSecondHalfLine > 0u)
+ {
+ if(isSecondHalf)
+ {
+ tmpLineLayout.numberOfCharactersInSecondHalfLine -= charactersPerGlyph;
+ tmpLineLayout.numberOfGlyphsInSecondHalfLine -= numberOfGLyphsInGroup;
+ }
+ else
+ {
+ tmpLineLayout.numberOfCharacters -= charactersPerGlyph;
+ tmpLineLayout.numberOfGlyphs -= numberOfGLyphsInGroup;
+ }
+
+ tmpLineLayout.penX = previousTmpPenX;
+ tmpLineLayout.previousAdvance = previousTmpAdvance;
+ tmpLineLayout.length = previousTmpLength;
+ tmpLineLayout.whiteSpaceLengthEndOfLine = previousTmpWhiteSpaceLengthEndOfLine;
+ }
+
+ if(ellipsisPosition == DevelText::EllipsisPosition::START && !isMultiline)
+ {
+ // Add part of the word to the line layout and shift the first glyph.
+ MergeLineLayout(lineLayout, tmpLineLayout, true);
+ }
+ else if(ellipsisPosition != DevelText::EllipsisPosition::START ||
+ (ellipsisPosition == DevelText::EllipsisPosition::START && (!completelyFill)))
+ {
+ // Add part of the word to the line layout.
+ MergeLineLayout(lineLayout, tmpLineLayout, false);
+ }
+ }
+ else
+ {
+ DALI_LOG_INFO(gLogFilter, Debug::Verbose, " Current word does not fit.\n");
+ }
+
+ DALI_LOG_INFO(gLogFilter, Debug::Verbose, "<--GetLineLayoutForBox.\n");
+
+ // Reorder the RTL line.
+ if(bidiParameters.isBidirectional)
+ {
+ ReorderBiDiLayout(parameters,
+ bidiParameters,
+ lineLayout,
+ lineLayout,
+ true,
+ enforceEllipsisInSingleLine);
+ }
+
+ return;
+ }
+
+ if((isMultiline || isLastGlyph) &&
+ (TextAbstraction::LINE_MUST_BREAK == lineBreakInfo))
+ {
+ LineLayout currentLineLayout = lineLayout;
+ oneHyphenLaidOut = false;
+
+ if(ellipsisPosition == DevelText::EllipsisPosition::START && !isMultiline)
+ {
+ // Must break the line. Update the line layout, shift the first glyph and return.
+ MergeLineLayout(lineLayout, tmpLineLayout, true);
+ }
+ else
+ {
+ // Must break the line. Update the line layout and return.
+ MergeLineLayout(lineLayout, tmpLineLayout, false);
+ }
+
+ // Reorder the RTL line.
+ if(bidiParameters.isBidirectional)
+ {
+ ReorderBiDiLayout(parameters,
+ bidiParameters,
+ currentLineLayout,
+ lineLayout,
+ false,
+ enforceEllipsisInSingleLine);
+ }
+
+ DALI_LOG_INFO(gLogFilter, Debug::Verbose, " Must break\n");
+ DALI_LOG_INFO(gLogFilter, Debug::Verbose, "<--GetLineLayoutForBox\n");
+
+ return;
+ }
+
+ if(isMultiline &&
+ (TextAbstraction::LINE_ALLOW_BREAK == lineBreakInfo))
+ {
+ oneHyphenLaidOut = false;
+ oneWordLaidOut = isWordLaidOut;
+ DALI_LOG_INFO(gLogFilter, Debug::Verbose, " One word laid-out\n");
+
+ // Current glyph is the last one of the current word.
+ // Add the temporal layout to the current one.
+ MergeLineLayout(lineLayout, tmpLineLayout, false);
+
+ tmpLineLayout.Clear();
+ }
+
+ if(isMultiline &&
+ ((isHyphenMode || (!oneWordLaidOut && isMixedMode))) &&
+ (TextAbstraction::LINE_HYPHENATION_BREAK == lineBreakInfo))
+ {
+ hyphenGlyph = GlyphInfo();
+ hyphenGlyph.fontId = glyphsBuffer[glyphIndex].fontId;
+
+ TextAbstraction::FontClient fontClient = TextAbstraction::FontClient::Get();
+ hyphenGlyph.index = fontClient.GetGlyphIndex(hyphenGlyph.fontId, HYPHEN_UNICODE);
+
+ mMetrics->GetGlyphMetrics(&hyphenGlyph, 1);
+
+ if((tmpLineLayout.length + hyphenGlyph.width) <= targetWidth)
+ {
+ hyphenIndex = glyphIndex;
+ oneHyphenLaidOut = true;
+
+ DALI_LOG_INFO(gLogFilter, Debug::Verbose, " One hyphen laid-out\n");
+
+ // Current glyph is the last one of the current word hyphen.
+ // Add the temporal layout to the current one.
+ MergeLineLayout(lineLayout, tmpLineLayout, false);
+
+ tmpLineLayout.Clear();
+ }
+ }
+
+ glyphIndex += numberOfGLyphsInGroup;
+ }
+
+ DALI_LOG_INFO(gLogFilter, Debug::Verbose, "<--GetLineLayoutForBox\n");
+ }
+
+ void SetGlyphPositions(const Parameters& layoutParameters,
+ Vector2* glyphPositionsBuffer,
+ const LineLayout& layout)
+ {
+ // Traverse the glyphs and set the positions.
+
+ const GlyphInfo* const glyphsBuffer = layoutParameters.textModel->mVisualModel->mGlyphs.Begin();
+ const float outlineWidth = static_cast<float>(layoutParameters.textModel->GetOutlineWidth());
+ const Length numberOfGlyphs = layout.numberOfGlyphs;
+ const float interGlyphExtraAdvance = layoutParameters.interGlyphExtraAdvance;
+
+ const GlyphIndex startIndexForGlyph = layout.glyphIndex;
+ const GlyphIndex startIndexForGlyphPositions = startIndexForGlyph - layoutParameters.startGlyphIndex;
+
+ // Check if the x bearing of the first character is negative.
+ // If it has a negative x bearing, it will exceed the boundaries of the actor,
+ // so the penX position needs to be moved to the right.
+ const GlyphInfo& glyph = *(glyphsBuffer + startIndexForGlyph);
+ float penX = -glyph.xBearing + mCursorWidth + outlineWidth; //
+
+ CalculateGlyphPositionsLTR(layoutParameters.textModel->mVisualModel,
+ layoutParameters.textModel->mLogicalModel,
+ interGlyphExtraAdvance,
+ numberOfGlyphs,
+ startIndexForGlyph, // startIndexForGlyph is the index of the first glyph in the line
+ startIndexForGlyphPositions,
+ glyphPositionsBuffer,
+ penX);
+
+ if(layout.isSplitToTwoHalves)
+ {
+ const GlyphIndex startIndexForGlyphInSecondHalf = layout.glyphIndexInSecondHalfLine;
+ const Length numberOfGlyphsInSecondHalfLine = layout.numberOfGlyphsInSecondHalfLine;
+ const GlyphIndex startIndexForGlyphPositionsnSecondHalf = layout.glyphIndexInSecondHalfLine - layoutParameters.startGlyphIndex;
+
+ CalculateGlyphPositionsLTR(layoutParameters.textModel->mVisualModel,
+ layoutParameters.textModel->mLogicalModel,
+ interGlyphExtraAdvance,
+ numberOfGlyphsInSecondHalfLine,
+ startIndexForGlyphInSecondHalf, // startIndexForGlyph is the index of the first glyph in the line
+ startIndexForGlyphPositionsnSecondHalf,
+ glyphPositionsBuffer,
+ penX);
+ }
+ }
+
+ void SetGlyphPositions(const Parameters& layoutParameters,
+ Vector2* glyphPositionsBuffer,
+ LayoutBidiParameters& layoutBidiParameters,
+ const LineLayout& layout)
+ {
+ const BidirectionalLineInfoRun& bidiLine = layoutParameters.textModel->mLogicalModel->mBidirectionalLineInfo[layoutBidiParameters.bidiLineIndex];
+ const GlyphInfo* const glyphsBuffer = layoutParameters.textModel->mVisualModel->mGlyphs.Begin();
+ const GlyphIndex* const charactersToGlyphsBuffer = layoutParameters.textModel->mVisualModel->mCharactersToGlyph.Begin();
+
+ CharacterIndex characterLogicalIndex = 0u;
+ CharacterIndex characterVisualIndex = bidiLine.characterRunForSecondHalfLine.characterIndex + *(bidiLine.visualToLogicalMapSecondHalf + characterLogicalIndex);
+ bool extendedToSecondHalf = false; // Whether the logical index is extended to second half
+
+ float penX = 0.f;
+
+ if(layout.isSplitToTwoHalves)
+ {
+ CalculateGlyphPositionsRTL(layoutParameters.textModel->mVisualModel,
+ layoutParameters.textModel->mLogicalModel,
+ layoutBidiParameters.bidiLineIndex,
+ layoutParameters.startGlyphIndex,
+ glyphPositionsBuffer,
+ characterVisualIndex,
+ characterLogicalIndex,
+ penX);
+ }
+
+ if(characterLogicalIndex == bidiLine.characterRunForSecondHalfLine.numberOfCharacters)