X-Git-Url: http://review.tizen.org/git/?p=platform%2Fcore%2Fuifw%2Fdali-toolkit.git;a=blobdiff_plain;f=dali-toolkit%2Finternal%2Ftext%2Flayouts%2Flayout-engine.cpp;h=39af47ce85a4a56eb4bfe19d9ae570306f919354;hp=6e8be71482946643f03601f72f0bdfe32655a4c2;hb=c427acac5f2616578c05987c99e7b430c9ab0137;hpb=b31354c6e542fbf4658964882bdd2591935e36f6 diff --git a/dali-toolkit/internal/text/layouts/layout-engine.cpp b/dali-toolkit/internal/text/layouts/layout-engine.cpp old mode 100755 new mode 100644 index 6e8be71..39af47c --- a/dali-toolkit/internal/text/layouts/layout-engine.cpp +++ b/dali-toolkit/internal/text/layouts/layout-engine.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017 Samsung Electronics Co., Ltd. + * Copyright (c) 2022 Samsung Electronics Co., Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,43 +19,58 @@ #include // EXTERNAL INCLUDES -#include -#include -#include #include +#include +#include +#include // INTERNAL INCLUDES -#include +#include #include #include +#include #include +#include namespace Dali { - namespace Toolkit { - namespace Text { +float GetLineHeight(const LineRun lineRun, bool isLastLine) +{ + // The line height is the addition of the line ascender, the line descender and the line spacing. + // However, the line descender has a negative value, hence the subtraction. + // In case this is the only/last line then line spacing should be ignored. + float lineHeight = lineRun.ascender - lineRun.descender; + + if(!isLastLine || lineRun.lineSpacing > 0) + { + lineHeight += lineRun.lineSpacing; + } + return lineHeight; +} namespace Layout { - namespace { - #if defined(DEBUG_ENABLED) - Debug::Filter* gLogFilter = Debug::Filter::New(Debug::Concise, true, "LOG_TEXT_LAYOUT"); +Debug::Filter* gLogFilter = Debug::Filter::New(Debug::Concise, true, "LOG_TEXT_LAYOUT"); #endif -const float MAX_FLOAT = std::numeric_limits::max(); -const bool RTL = true; -const float LINE_SPACING= 0.f; +const float MAX_FLOAT = std::numeric_limits::max(); +const CharacterDirection LTR = false; +const CharacterDirection RTL = !LTR; +const float LINE_SPACING = 0.f; +const float MIN_LINE_SIZE = 0.f; +const Character HYPHEN_UNICODE = 0x002D; +const float RELATIVE_LINE_SIZE = 1.f; -inline bool isEmptyLineAtLast( const Vector& lines, const Vector::Iterator& line ) +inline bool isEmptyLineAtLast(const Vector& lines, const Vector::Iterator& line) { - return ( (*line).characterRun.numberOfCharacters == 0 && line + 1u == lines.End() ); + return ((*line).characterRun.numberOfCharacters == 0 && line + 1u == lines.End()); } } //namespace @@ -66,84 +81,171 @@ inline bool isEmptyLineAtLast( const Vector& lines, const Vector mDefaultLineSize) + { + if(relativeLineSize < 1) + { + //subtract the difference (always will be positive) + lineSpacing -= (textSize - relTextSize); + } + else + { + //reverse the addition in the top. + if(mDefaultLineSize > textSize) + { + lineSpacing -= mDefaultLineSize - textSize; + } + + //add difference instead + lineSpacing += relTextSize - textSize; + } + } + + return lineSpacing; + } + + /** * @brief Updates the line ascender and descender with the metrics of a new font. * * @param[in] glyphMetrics The metrics of the new font. * @param[in,out] lineLayout The line layout. */ - void UpdateLineHeight( const GlyphMetrics& glyphMetrics, LineLayout& lineLayout ) + void UpdateLineHeight(const GlyphMetrics& glyphMetrics, LineLayout& lineLayout) { Text::FontMetrics fontMetrics; - if( 0u != glyphMetrics.fontId ) + if(0u != glyphMetrics.fontId) { - mMetrics->GetFontMetrics( glyphMetrics.fontId, fontMetrics ); + mMetrics->GetFontMetrics(glyphMetrics.fontId, fontMetrics); } else { - fontMetrics.ascender = glyphMetrics.fontHeight; - fontMetrics.descender = 0.f; - fontMetrics.height = fontMetrics.ascender; - fontMetrics.underlinePosition = 0.f; + fontMetrics.ascender = glyphMetrics.fontHeight; + fontMetrics.descender = 0.f; + fontMetrics.height = fontMetrics.ascender; + fontMetrics.underlinePosition = 0.f; fontMetrics.underlineThickness = 1.f; } // Sets the maximum ascender. - lineLayout.ascender = std::max( lineLayout.ascender, fontMetrics.ascender ); + lineLayout.ascender = std::max(lineLayout.ascender, fontMetrics.ascender); // Sets the minimum descender. - lineLayout.descender = std::min( lineLayout.descender, fontMetrics.descender ); + lineLayout.descender = std::min(lineLayout.descender, fontMetrics.descender); - // set the line spacing - lineLayout.lineSpacing = mDefaultLineSpacing; + lineLayout.lineSpacing = GetLineSpacing(lineLayout.ascender + -lineLayout.descender, lineLayout.relativeLineSize); } /** @@ -151,53 +253,449 @@ struct Engine::Impl * * @param[in,out] lineLayout The line layout. * @param[in] tmpLineLayout A temporary line layout. + * @param[in] isShifted Whether to shift first glyph and character indices. */ - void MergeLineLayout( LineLayout& lineLayout, - const LineLayout& tmpLineLayout ) + void MergeLineLayout(LineLayout& lineLayout, + const LineLayout& tmpLineLayout, + bool isShifted) { lineLayout.numberOfCharacters += tmpLineLayout.numberOfCharacters; lineLayout.numberOfGlyphs += tmpLineLayout.numberOfGlyphs; - lineLayout.penX = tmpLineLayout.penX; + lineLayout.penX = tmpLineLayout.penX; lineLayout.previousAdvance = tmpLineLayout.previousAdvance; - lineLayout.length = tmpLineLayout.length; - lineLayout.wsLengthEndOfLine = tmpLineLayout.wsLengthEndOfLine; + lineLayout.length = tmpLineLayout.length; + lineLayout.whiteSpaceLengthEndOfLine = tmpLineLayout.whiteSpaceLengthEndOfLine; // Sets the maximum ascender. - lineLayout.ascender = std::max( lineLayout.ascender, tmpLineLayout.ascender ); + lineLayout.ascender = std::max(lineLayout.ascender, tmpLineLayout.ascender); // Sets the minimum descender. - lineLayout.descender = std::min( lineLayout.descender, tmpLineLayout.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(parameters.textModel->GetOutlineWidth()); + const GlyphIndex lastGlyphOfParagraphPlusOne = parameters.startGlyphIndex + parameters.numberOfGlyphs; + const float modelCharacterSpacing = parameters.textModel->mVisualModel->GetCharacterSpacing(); + + // Get the character-spacing runs. + const Vector& 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& 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& 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& 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 lais out text as it were left to right. At this point is not possible to reorder the line - * because the number of characters of the line is not known (one of the responsabilities of this method - * is calculate that). Due to glyph's 'x' bearing, width and advance, when right to left or mixed right to left - * and left to right text is laid-out, it can be small differences in the line length. One solution is to - * reorder and re-lay out the text after this method and add or remove one extra glyph if needed. However, - * this method calculates which are the first and last glyphs of the line (the ones that causes the - * differences). This is a good point to check if there is problems with the text exceeding the boundaries - * of the control when there is right to left text. + * @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,out] paragraphDirection in: the current paragraph's direction, out: the next paragraph's direction. Is set after a must break. * @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, - LineLayout& lineLayout, - CharacterDirection& paragraphDirection, - bool completelyFill ) + 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 ); + 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(parameters.textModel->GetOutlineWidth()); + const Length totalNumberOfGlyphs = parameters.textModel->mVisualModel->mGlyphs.Count(); - const bool isMultiline = mLayout == MULTI_LINE_BOX; - const bool isWordLaidOut = parameters.lineWrapMode == Text::LineWrap::WORD; + 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; @@ -207,19 +705,35 @@ struct Engine::Impl // 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, - parameters.charactersPerGlyphBuffer ); + 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& glyphToCharacterMap = parameters.textModel->mVisualModel->mGlyphsToCharacters; + const CharacterIndex* glyphToCharacterMapBuffer = glyphToCharacterMap.Begin(); + + // Get the character-spacing runs. + const Vector& characterSpacingGlyphRuns = parameters.textModel->mVisualModel->GetCharacterSpacingGlyphRuns(); GlyphMetrics glyphMetrics; - GetGlyphsMetrics( lineLayout.glyphIndex, - numberOfGLyphsInGroup, - glyphMetrics, - parameters.glyphsBuffer, - mMetrics ); + 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 = *( parameters.glyphsToCharactersBuffer + lineLayout.glyphIndex ); + lineLayout.characterIndex = *(glyphsToCharactersBuffer + lineLayout.glyphIndex); // Stores temporary line layout which has not been added to the final line layout. LineLayout tmpLineLayout; @@ -229,185 +743,464 @@ struct Engine::Impl // 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 + parameters.outlineWidth; + tmpLineLayout.penX = -glyphMetrics.xBearing + mCursorWidth + outlineWidth; - // Initialize the advance of the previous glyph. - tmpLineLayout.previousAdvance = 0.f; + tmpLineLayout.relativeLineSize = lineLayout.relativeLineSize; // Calculate the line height if there is no characters. FontId lastFontId = glyphMetrics.fontId; - UpdateLineHeight( glyphMetrics, tmpLineLayout ); + UpdateLineHeight(glyphMetrics, tmpLineLayout); - bool oneWordLaidOut = false; + bool oneWordLaidOut = false; + bool oneHyphenLaidOut = false; + GlyphIndex hyphenIndex = 0; + GlyphInfo hyphenGlyph; - for( GlyphIndex glyphIndex = lineLayout.glyphIndex; - glyphIndex < lastGlyphOfParagraphPlusOne; ) + for(GlyphIndex glyphIndex = lineLayout.glyphIndex; + glyphIndex < lastGlyphOfParagraphPlusOne;) { - DALI_LOG_INFO( gLogFilter, Debug::Verbose, " glyph index : %d\n", glyphIndex ); + 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, - parameters.charactersPerGlyphBuffer ); + const Length numberOfGLyphsInGroup = GetNumberOfGlyphsOfGroup(glyphIndex, + lastGlyphOfParagraphPlusOne, + charactersPerGlyphBuffer); GlyphMetrics glyphMetrics; - GetGlyphsMetrics( glyphIndex, - numberOfGLyphsInGroup, - glyphMetrics, - parameters.glyphsBuffer, - mMetrics ); + 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 == parameters.totalNumberOfGlyphs; + 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 ) + if(lastFontId != glyphMetrics.fontId) { - UpdateLineHeight( glyphMetrics, tmpLineLayout ); + 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 = *( parameters.charactersPerGlyphBuffer + glyphIndex + numberOfGLyphsInGroup - 1u ); - const bool hasCharacters = charactersPerGlyph > 0u; - const CharacterIndex characterFirstIndex = *( parameters.glyphsToCharactersBuffer + glyphIndex ); - const CharacterIndex characterLastIndex = characterFirstIndex + ( hasCharacters ? charactersPerGlyph - 1u : 0u ); + 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 ? *( parameters.lineBreakInfoBuffer + characterLastIndex ) : TextAbstraction::LINE_NO_BREAK; + const LineBreakInfo lineBreakInfo = hasCharacters ? *(lineBreakInfoBuffer + characterLastIndex) : TextAbstraction::LINE_NO_BREAK; - // Increase the number of characters. - tmpLineLayout.numberOfCharacters += charactersPerGlyph; + if(isSecondHalf) + { + // Increase the number of characters. + tmpLineLayout.numberOfCharactersInSecondHalfLine += charactersPerGlyph; - // Increase the number of glyphs. - tmpLineLayout.numberOfGlyphs += numberOfGLyphsInGroup; + // 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 = *( parameters.textBuffer + characterFirstIndex ); - const bool isWhiteSpace = TextAbstraction::IsWhiteSpace( character ); + 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 previousTmpWsLengthEndOfLine = tmpLineLayout.wsLengthEndOfLine; + const float previousTmpPenX = tmpLineLayout.penX; + const float previousTmpAdvance = tmpLineLayout.previousAdvance; + const float previousTmpLength = tmpLineLayout.length; + const float previousTmpWhiteSpaceLengthEndOfLine = tmpLineLayout.whiteSpaceLengthEndOfLine; - if( isWhiteSpace ) + if(isWhiteSpace) { // Add the length to the length of white spaces at the end of the line. - tmpLineLayout.wsLengthEndOfLine += glyphMetrics.advance; // The advance is used as the width is always zero for the white spaces. + tmpLineLayout.whiteSpaceLengthEndOfLine += glyphMetrics.advance; + // The advance is used as the width is always zero for the white spaces. } else { - tmpLineLayout.penX += tmpLineLayout.previousAdvance + tmpLineLayout.wsLengthEndOfLine; - tmpLineLayout.previousAdvance = ( glyphMetrics.advance + parameters.interGlyphExtraAdvance ); - tmpLineLayout.length = tmpLineLayout.penX + glyphMetrics.xBearing + glyphMetrics.width; + 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.wsLengthEndOfLine = 0.f; + 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( ( completelyFill || isMultiline ) && !isWhiteSpace && - ( tmpLineLayout.length > parameters.boundingBox.width ) ) + if((ellipsisPosition == DevelText::EllipsisPosition::START || + (ellipsisPosition == DevelText::EllipsisPosition::MIDDLE && isSecondHalf)) && + completelyFill && !isMultiline && + (tmpLineLayout.length + tmpLineLayout.whiteSpaceLengthEndOfLine > targetWidth)) { - // Current word does not fit in the box's width. - if( !oneWordLaidOut || completelyFill ) + GlyphIndex glyphIndexToRemove = isSecondHalf ? tmpLineLayout.glyphIndexInSecondHalfLine : tmpLineLayout.glyphIndex; + + while(tmpLineLayout.length + tmpLineLayout.whiteSpaceLengthEndOfLine > targetWidth && glyphIndexToRemove < glyphIndex) { - DALI_LOG_INFO( gLogFilter, Debug::Verbose, " Break the word by character\n" ); + 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; - // The word doesn't fit in the control's width. It needs to be split by character. - if( tmpLineLayout.numberOfGlyphs > 0u ) + // 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; + } - tmpLineLayout.penX = previousTmpPenX; - tmpLineLayout.previousAdvance = previousTmpAdvance; - tmpLineLayout.length = previousTmpLength; - tmpLineLayout.wsLengthEndOfLine = previousTmpWsLengthEndOfLine; + 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)); } - // Add part of the word to the line layout. - MergeLineLayout( lineLayout, tmpLineLayout ); + 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, " Current word does not fit.\n"); } - DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--GetLineLayoutForBox.\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 ) ) + if((isMultiline || isLastGlyph) && + (TextAbstraction::LINE_MUST_BREAK == lineBreakInfo)) { - // Must break the line. Update the line layout and return. - MergeLineLayout( lineLayout, tmpLineLayout ); + LineLayout currentLineLayout = lineLayout; + oneHyphenLaidOut = false; - // Set the next paragraph's direction. - if( !isLastGlyph && - ( NULL != parameters.characterDirectionBuffer ) ) + 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 { - paragraphDirection = *( parameters.characterDirectionBuffer + 1u + characterLastIndex ); + // Must break the line. Update the line layout and return. + MergeLineLayout(lineLayout, tmpLineLayout, false); } - DALI_LOG_INFO( gLogFilter, Debug::Verbose, " Must break\n" ); - DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--GetLineLayoutForBox\n" ); + // 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 ) ) + if(isMultiline && + (TextAbstraction::LINE_ALLOW_BREAK == lineBreakInfo)) { - oneWordLaidOut = isWordLaidOut; - DALI_LOG_INFO( gLogFilter, Debug::Verbose, " One word laid-out\n" ); + 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 ); + 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" ); + DALI_LOG_INFO(gLogFilter, Debug::Verbose, "<--GetLineLayoutForBox\n"); } - void SetGlyphPositions( const GlyphInfo* const glyphsBuffer, - Length numberOfGlyphs, - float outlineWidth, - float interGlyphExtraAdvance, - Vector2* glyphPositionsBuffer ) + 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(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); + } + } - const GlyphInfo& glyph = *glyphsBuffer; - float penX = -glyph.xBearing + mCursorWidth + outlineWidth; + 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 - for( GlyphIndex i = 0u; i < numberOfGlyphs; ++i ) + float penX = 0.f; + + if(layout.isSplitToTwoHalves) { - const GlyphInfo& glyph = *( glyphsBuffer + i ); - Vector2& position = *( glyphPositionsBuffer + i ); + CalculateGlyphPositionsRTL(layoutParameters.textModel->mVisualModel, + layoutParameters.textModel->mLogicalModel, + layoutBidiParameters.bidiLineIndex, + layoutParameters.startGlyphIndex, + glyphPositionsBuffer, + characterVisualIndex, + characterLogicalIndex, + penX); + } - position.x = std::roundf( penX + glyph.xBearing ); - position.y = -glyph.yBearing; + if(characterLogicalIndex == bidiLine.characterRunForSecondHalfLine.numberOfCharacters) + { + extendedToSecondHalf = true; + characterLogicalIndex = 0u; + characterVisualIndex = bidiLine.characterRun.characterIndex + *(bidiLine.visualToLogicalMap + characterLogicalIndex); + + CalculateGlyphPositionsRTL(layoutParameters.textModel->mVisualModel, + layoutParameters.textModel->mLogicalModel, + layoutBidiParameters.bidiLineIndex, + layoutParameters.startGlyphIndex, + glyphPositionsBuffer, + characterVisualIndex, + characterLogicalIndex, + penX); + } + + const GlyphIndex glyphIndex = *(charactersToGlyphsBuffer + characterVisualIndex); + const GlyphInfo& glyph = *(glyphsBuffer + glyphIndex); + + penX += -glyph.xBearing; - penX += ( glyph.advance + interGlyphExtraAdvance ); + // Traverses the characters of the right to left paragraph. + if(layout.isSplitToTwoHalves && !extendedToSecondHalf) + { + TraversesCharactersForGlyphPositionsRTL(layoutParameters.textModel->mVisualModel, + layoutParameters.textModel->mLogicalModel->mText.Begin(), + layoutParameters.startGlyphIndex, + layoutParameters.interGlyphExtraAdvance, + bidiLine.characterRunForSecondHalfLine, + bidiLine.visualToLogicalMapSecondHalf, + glyphPositionsBuffer, + characterLogicalIndex, + penX); } + + characterLogicalIndex = extendedToSecondHalf ? characterLogicalIndex : 0u; + + TraversesCharactersForGlyphPositionsRTL(layoutParameters.textModel->mVisualModel, + layoutParameters.textModel->mLogicalModel->mText.Begin(), + layoutParameters.startGlyphIndex, + layoutParameters.interGlyphExtraAdvance, + bidiLine.characterRun, + bidiLine.visualToLogicalMap, + glyphPositionsBuffer, + characterLogicalIndex, + penX); } /** @@ -420,22 +1213,22 @@ struct Engine::Impl * * @return Pointer to either lines or newLines. */ - LineRun* ResizeLinesBuffer( Vector& lines, - Vector& newLines, - Length& linesCapacity, - bool updateCurrentBuffer ) + LineRun* ResizeLinesBuffer(Vector& lines, + Vector& newLines, + Length& linesCapacity, + bool updateCurrentBuffer) { - LineRun* linesBuffer = NULL; + LineRun* linesBuffer = nullptr; // Reserve more space for the next lines. linesCapacity *= 2u; - if( updateCurrentBuffer ) + if(updateCurrentBuffer) { - newLines.Resize( linesCapacity ); + newLines.Resize(linesCapacity); linesBuffer = newLines.Begin(); } else { - lines.Resize( linesCapacity ); + lines.Resize(linesCapacity); linesBuffer = lines.Begin(); } @@ -454,39 +1247,47 @@ struct Engine::Impl * @param[in] penY The vertical layout position. * @param[in] currentParagraphDirection The current paragraph's direction. * @param[in,out] isAutoScrollEnabled If the isAutoScrollEnabled is true and the height of the text exceeds the boundaries of the control the text is elided and the isAutoScrollEnabled is set to false to disable the autoscroll + * @param[in] ellipsisPosition Where is the location the text elide * * return Whether the line is ellipsized. */ - bool EllipsisLine( const Parameters& layoutParameters, - const LineLayout& layout, - Size& layoutSize, - LineRun* linesBuffer, - Vector2* glyphPositionsBuffer, - Length& numberOfLines, - float penY, - CharacterDirection currentParagraphDirection, - bool& isAutoScrollEnabled ) + bool EllipsisLine(const Parameters& layoutParameters, + LayoutBidiParameters& layoutBidiParameters, + const LineLayout& layout, + Size& layoutSize, + LineRun* linesBuffer, + Vector2* glyphPositionsBuffer, + Length& numberOfLines, + float penY, + bool& isAutoScrollEnabled, + bool isAutoScrollMaxTextureExceeded, + DevelText::EllipsisPosition::Type ellipsisPosition, + bool enforceEllipsisInSingleLine) { - const bool ellipsis = isAutoScrollEnabled ? ( penY - layout.descender > layoutParameters.boundingBox.height ) : - ( ( penY - layout.descender > layoutParameters.boundingBox.height ) || - ( ( mLayout == SINGLE_LINE_BOX ) && - ( layout.length > layoutParameters.boundingBox.width ) ) ); - - if( ellipsis ) + const bool ellipsis = enforceEllipsisInSingleLine || (isAutoScrollEnabled ? isAutoScrollMaxTextureExceeded : ((penY - layout.descender > layoutParameters.boundingBox.height) || ((mLayout == SINGLE_LINE_BOX) && (layout.length > layoutParameters.boundingBox.width)))); + const bool isMultiline = !enforceEllipsisInSingleLine && (mLayout == MULTI_LINE_BOX); + if(ellipsis && (ellipsisPosition == DevelText::EllipsisPosition::END || !isMultiline)) { - isAutoScrollEnabled = false; - // Do not layout more lines if ellipsis is enabled. + if(penY - layout.descender > layoutParameters.boundingBox.height) + { + // Even if auto scroll is enabled and text is bigger than max texture size, + // if the the height is small, auto scroll should not work. + isAutoScrollEnabled = false; + } + // Do not layout more lines if ellipsis is enabled. // The last line needs to be completely filled with characters. // Part of a word may be used. - LineRun* lineRun = NULL; + LineRun* lineRun = nullptr; LineLayout ellipsisLayout; - if( 0u != numberOfLines ) + + ellipsisLayout.relativeLineSize = layout.relativeLineSize; + + if(0u != numberOfLines) { // Get the last line and layout it again with the 'completelyFill' flag to true. - lineRun = linesBuffer + ( numberOfLines - 1u ); - + lineRun = linesBuffer + (numberOfLines - 1u); penY -= layout.ascender - lineRun->descender + lineRun->lineSpacing; ellipsisLayout.glyphIndex = lineRun->glyphRun.glyphIndex; @@ -497,37 +1298,100 @@ struct Engine::Impl lineRun = linesBuffer; lineRun->glyphRun.glyphIndex = 0u; - ellipsisLayout.glyphIndex = 0u; + ellipsisLayout.glyphIndex = 0u; + lineRun->isSplitToTwoHalves = false; ++numberOfLines; } - GetLineLayoutForBox( layoutParameters, - ellipsisLayout, - currentParagraphDirection, - true ); + GetLineLayoutForBox(layoutParameters, + layoutBidiParameters, + ellipsisLayout, + true, + ellipsisPosition, + enforceEllipsisInSingleLine, + true); - lineRun->glyphRun.numberOfGlyphs = ellipsisLayout.numberOfGlyphs; - lineRun->characterRun.characterIndex = ellipsisLayout.characterIndex; + if(ellipsisPosition == DevelText::EllipsisPosition::START && !isMultiline) + { + lineRun->glyphRun.glyphIndex = ellipsisLayout.glyphIndex; + } + + lineRun->glyphRun.numberOfGlyphs = ellipsisLayout.numberOfGlyphs; + lineRun->characterRun.characterIndex = ellipsisLayout.characterIndex; lineRun->characterRun.numberOfCharacters = ellipsisLayout.numberOfCharacters; - lineRun->width = ellipsisLayout.length; - lineRun->extraLength = std::ceil( ellipsisLayout.wsLengthEndOfLine ); - lineRun->ascender = ellipsisLayout.ascender; - lineRun->descender = ellipsisLayout.descender; - lineRun->direction = !RTL; - lineRun->ellipsis = true; + lineRun->width = ellipsisLayout.length; + lineRun->extraLength = std::ceil(ellipsisLayout.whiteSpaceLengthEndOfLine); + lineRun->ascender = ellipsisLayout.ascender; + lineRun->descender = ellipsisLayout.descender; + lineRun->ellipsis = true; + + lineRun->isSplitToTwoHalves = ellipsisLayout.isSplitToTwoHalves; + lineRun->glyphRunSecondHalf.glyphIndex = ellipsisLayout.glyphIndexInSecondHalfLine; + lineRun->glyphRunSecondHalf.numberOfGlyphs = ellipsisLayout.numberOfGlyphsInSecondHalfLine; + lineRun->characterRunForSecondHalfLine.characterIndex = ellipsisLayout.characterIndexInSecondHalfLine; + lineRun->characterRunForSecondHalfLine.numberOfCharacters = ellipsisLayout.numberOfCharactersInSecondHalfLine; layoutSize.width = layoutParameters.boundingBox.width; - if( layoutSize.height < Math::MACHINE_EPSILON_1000 ) + if(layoutSize.height < Math::MACHINE_EPSILON_1000) { - layoutSize.height += ( lineRun->ascender + -lineRun->descender ) + lineRun->lineSpacing; + layoutSize.height += GetLineHeight(*lineRun, true); } + else + { + //when we apply ellipsis, the last line should not take negative linespacing into account for layoutSize.height calculation + //usually we don't includ it in normal cases using GetLineHeight() + if(lineRun->lineSpacing < 0) + { + layoutSize.height -= lineRun->lineSpacing; + } + } + + const Vector& bidirectionalLinesInfo = layoutParameters.textModel->mLogicalModel->mBidirectionalLineInfo; - SetGlyphPositions( layoutParameters.glyphsBuffer + lineRun->glyphRun.glyphIndex, - ellipsisLayout.numberOfGlyphs, - layoutParameters.outlineWidth, - layoutParameters.interGlyphExtraAdvance, - glyphPositionsBuffer + lineRun->glyphRun.glyphIndex - layoutParameters.startGlyphIndex ); + if(layoutBidiParameters.isBidirectional) + { + layoutBidiParameters.bidiLineIndex = 0u; + for(Vector::ConstIterator it = bidirectionalLinesInfo.Begin(), + endIt = bidirectionalLinesInfo.End(); + it != endIt; + ++it, ++layoutBidiParameters.bidiLineIndex) + { + const BidirectionalLineInfoRun& run = *it; + //To handle case when the laid characters exist in next line. + //More than one BidirectionalLineInfoRun could start with same character. + //When need to check also numberOfCharacters in line. + //Note: This fixed the incorrect view of extra spaces of RTL as in Arabic then view ellipsis glyph + if(ellipsisLayout.characterIndex == run.characterRun.characterIndex && + ellipsisLayout.numberOfCharacters == run.characterRun.numberOfCharacters && + ellipsisLayout.characterIndexInSecondHalfLine == run.characterRunForSecondHalfLine.characterIndex && + ellipsisLayout.numberOfCharactersInSecondHalfLine == run.characterRunForSecondHalfLine.numberOfCharacters) + { + // Found where to insert the bidi line info. + break; + } + } + } + + const BidirectionalLineInfoRun* const bidirectionalLineInfo = (layoutBidiParameters.isBidirectional && !bidirectionalLinesInfo.Empty()) ? &bidirectionalLinesInfo[layoutBidiParameters.bidiLineIndex] : nullptr; + + if((nullptr != bidirectionalLineInfo) && + !bidirectionalLineInfo->isIdentity && + (ellipsisLayout.characterIndex == bidirectionalLineInfo->characterRun.characterIndex)) + { + lineRun->direction = RTL; + SetGlyphPositions(layoutParameters, + glyphPositionsBuffer, + layoutBidiParameters, + ellipsisLayout); + } + else + { + lineRun->direction = LTR; + SetGlyphPositions(layoutParameters, + glyphPositionsBuffer, + ellipsisLayout); + } } return ellipsis; @@ -544,58 +1408,47 @@ struct Engine::Impl * @param[in,out] numberOfLines The number of laid-out lines. * @param[in] isLastLine Whether the laid-out line is the last one. */ - void UpdateTextLayout( const Parameters& layoutParameters, - const LineLayout& layout, - Size& layoutSize, - LineRun* linesBuffer, - GlyphIndex index, - Length& numberOfLines, - bool isLastLine ) + void UpdateTextLayout(const Parameters& layoutParameters, + const LineLayout& layout, + Size& layoutSize, + LineRun* linesBuffer, + GlyphIndex index, + Length& numberOfLines, + bool isLastLine) { - LineRun& lineRun = *( linesBuffer + numberOfLines ); + LineRun& lineRun = *(linesBuffer + numberOfLines); ++numberOfLines; - lineRun.glyphRun.glyphIndex = index; - lineRun.glyphRun.numberOfGlyphs = layout.numberOfGlyphs; - lineRun.characterRun.characterIndex = layout.characterIndex; + lineRun.glyphRun.glyphIndex = index; + lineRun.glyphRun.numberOfGlyphs = layout.numberOfGlyphs; + lineRun.characterRun.characterIndex = layout.characterIndex; lineRun.characterRun.numberOfCharacters = layout.numberOfCharacters; - lineRun.lineSpacing = mDefaultLineSpacing; - - if( isLastLine && !layoutParameters.isLastNewParagraph ) - { - const float width = layout.length + layout.wsLengthEndOfLine; - if( MULTI_LINE_BOX == mLayout ) - { - lineRun.width = ( width > layoutParameters.boundingBox.width ) ? layoutParameters.boundingBox.width : width; - } - else - { - lineRun.width = width; - } + lineRun.width = layout.length; + lineRun.extraLength = std::ceil(layout.whiteSpaceLengthEndOfLine); - lineRun.extraLength = 0.f; - } - else - { - lineRun.width = layout.length; - lineRun.extraLength = std::ceil( layout.wsLengthEndOfLine ); - } + lineRun.isSplitToTwoHalves = layout.isSplitToTwoHalves; + lineRun.glyphRunSecondHalf.glyphIndex = layout.glyphIndexInSecondHalfLine; + lineRun.glyphRunSecondHalf.numberOfGlyphs = layout.numberOfGlyphsInSecondHalfLine; + lineRun.characterRunForSecondHalfLine.characterIndex = layout.characterIndexInSecondHalfLine; + lineRun.characterRunForSecondHalfLine.numberOfCharacters = layout.numberOfCharactersInSecondHalfLine; // Rounds upward to avoid a non integer size. - lineRun.width = std::ceil( lineRun.width ); + lineRun.width = std::ceil(lineRun.width); - lineRun.ascender = layout.ascender; + lineRun.ascender = layout.ascender; lineRun.descender = layout.descender; - lineRun.direction = !RTL; - lineRun.ellipsis = false; + lineRun.direction = layout.direction; + lineRun.ellipsis = false; + + lineRun.lineSpacing = GetLineSpacing(lineRun.ascender + -lineRun.descender, layout.relativeLineSize); // Update the actual size. - if( lineRun.width > layoutSize.width ) + if(lineRun.width > layoutSize.width) { layoutSize.width = lineRun.width; } - layoutSize.height += ( lineRun.ascender + -lineRun.descender ) + lineRun.lineSpacing; + layoutSize.height += GetLineHeight(lineRun, isLastLine); } /** @@ -608,39 +1461,46 @@ struct Engine::Impl * @param[in,out] linesBuffer Pointer to the line's buffer. * @param[in,out] numberOfLines The number of laid-out lines. */ - void UpdateTextLayout( const Parameters& layoutParameters, - CharacterIndex characterIndex, - GlyphIndex glyphIndex, - Size& layoutSize, - LineRun* linesBuffer, - Length& numberOfLines ) + void UpdateTextLayout(const Parameters& layoutParameters, + CharacterIndex characterIndex, + GlyphIndex glyphIndex, + Size& layoutSize, + LineRun* linesBuffer, + Length& numberOfLines) { + const Vector& glyphs = layoutParameters.textModel->mVisualModel->mGlyphs; + // Need to add a new line with no characters but with height to increase the layoutSize.height - const GlyphInfo& glyphInfo = *( layoutParameters.glyphsBuffer + layoutParameters.totalNumberOfGlyphs - 1u ); + const GlyphInfo& glyphInfo = glyphs[glyphs.Count() - 1u]; Text::FontMetrics fontMetrics; - if( 0u != glyphInfo.fontId ) + if(0u != glyphInfo.fontId) { - mMetrics->GetFontMetrics( glyphInfo.fontId, fontMetrics ); + mMetrics->GetFontMetrics(glyphInfo.fontId, fontMetrics); } - LineRun& lineRun = *( linesBuffer + numberOfLines ); + LineRun& lineRun = *(linesBuffer + numberOfLines); ++numberOfLines; - lineRun.glyphRun.glyphIndex = glyphIndex; - lineRun.glyphRun.numberOfGlyphs = 0u; - lineRun.characterRun.characterIndex = characterIndex; + lineRun.glyphRun.glyphIndex = glyphIndex; + lineRun.glyphRun.numberOfGlyphs = 0u; + lineRun.characterRun.characterIndex = characterIndex; lineRun.characterRun.numberOfCharacters = 0u; - lineRun.width = 0.f; - lineRun.ascender = fontMetrics.ascender; - lineRun.descender = fontMetrics.descender; - lineRun.extraLength = 0.f; - lineRun.alignmentOffset = 0.f; - lineRun.direction = !RTL; - lineRun.ellipsis = false; - lineRun.lineSpacing = mDefaultLineSpacing; - - layoutSize.height += ( lineRun.ascender + -lineRun.descender ) + lineRun.lineSpacing; + lineRun.width = 0.f; + lineRun.ascender = fontMetrics.ascender; + lineRun.descender = fontMetrics.descender; + lineRun.extraLength = 0.f; + lineRun.alignmentOffset = 0.f; + lineRun.direction = LTR; + lineRun.ellipsis = false; + + BoundedParagraphRun currentParagraphRun; + LineLayout tempLineLayout; + (GetBoundedParagraph(layoutParameters.textModel->GetBoundedParagraphRuns(), characterIndex, currentParagraphRun) ? SetRelativeLineSize(¤tParagraphRun, tempLineLayout) : SetRelativeLineSize(nullptr, tempLineLayout)); + + lineRun.lineSpacing = GetLineSpacing(lineRun.ascender + -lineRun.descender, tempLineLayout.relativeLineSize); + + layoutSize.height += GetLineHeight(lineRun, true); } /** @@ -649,22 +1509,23 @@ struct Engine::Impl * @param[in] lines The vector of lines (before the new laid-out lines are inserted). * @param[in,out] layoutSize The text's layout size. */ - void UpdateLayoutSize( const Vector& lines, - Size& layoutSize ) + void UpdateLayoutSize(const Vector& lines, + Size& layoutSize) { - for( Vector::ConstIterator it = lines.Begin(), - endIt = lines.End(); - it != endIt; - ++it ) + for(Vector::ConstIterator it = lines.Begin(), + endIt = lines.End(); + it != endIt; + ++it) { - const LineRun& line = *it; + const LineRun& line = *it; + bool isLastLine = (it + 1 == endIt); - if( line.width > layoutSize.width ) + if(line.width > layoutSize.width) { layoutSize.width = line.width; } - layoutSize.height += ( line.ascender + -line.descender ) + line.lineSpacing; + layoutSize.height += GetLineHeight(line, isLastLine); } } @@ -676,20 +1537,20 @@ struct Engine::Impl * @param[in] characterOffset The offset to be added to the runs of characters. * @param[in] glyphOffset The offset to be added to the runs of glyphs. */ - void UpdateLineIndexOffsets( const Parameters& layoutParameters, - Vector& lines, - Length characterOffset, - Length glyphOffset ) + void UpdateLineIndexOffsets(const Parameters& layoutParameters, + Vector& lines, + Length characterOffset, + Length glyphOffset) { // Update the glyph and character runs. - for( Vector::Iterator it = lines.Begin() + layoutParameters.startLineIndex, - endIt = lines.End(); - it != endIt; - ++it ) + for(Vector::Iterator it = lines.Begin() + layoutParameters.startLineIndex, + endIt = lines.End(); + it != endIt; + ++it) { LineRun& line = *it; - line.glyphRun.glyphIndex = glyphOffset; + line.glyphRun.glyphIndex = glyphOffset; line.characterRun.characterIndex = characterOffset; glyphOffset += line.glyphRun.numberOfGlyphs; @@ -697,363 +1558,536 @@ struct Engine::Impl } } - bool LayoutText( const Parameters& layoutParameters, - Vector& glyphPositions, - Vector& lines, - Size& layoutSize, - bool elideTextEnabled, - bool& isAutoScrollEnabled ) + /** + * @brief Sets the relative line size for the LineLayout + * + * @param[in] currentParagraphRun Contains the bounded paragraph for this line layout. + * @param[in,out] lineLayout The line layout to be updated. + */ + void SetRelativeLineSize(BoundedParagraphRun* currentParagraphRun, LineLayout& lineLayout) { - DALI_LOG_INFO( gLogFilter, Debug::Verbose, "-->LayoutText\n" ); - DALI_LOG_INFO( gLogFilter, Debug::Verbose, " box size %f, %f\n", layoutParameters.boundingBox.width, layoutParameters.boundingBox.height ); + lineLayout.relativeLineSize = mRelativeLineSize; - if( 0u == layoutParameters.numberOfGlyphs ) + if(currentParagraphRun != nullptr && currentParagraphRun->relativeLineSizeDefined) + { + lineLayout.relativeLineSize = currentParagraphRun->relativeLineSize; + } + } + + /** + * @brief Get the bounded paragraph for the characterIndex if exists. + * + * @param[in] boundedParagraphRuns The bounded paragraph list to search in. + * @param[in] characterIndex The character index to get bounded paragraph for. + * @param[out] currentParagraphRun Contains the bounded paragraph if found for the characterIndex. + * + * @return returns true if a bounded paragraph was found. + */ + bool GetBoundedParagraph(const Vector boundedParagraphRuns, CharacterIndex characterIndex, BoundedParagraphRun& currentParagraphRun) + { + for(Vector::Iterator it = boundedParagraphRuns.Begin(), + endIt = boundedParagraphRuns.End(); + it != endIt; + ++it) + { + BoundedParagraphRun& tempParagraphRun = *it; + + if(characterIndex >= tempParagraphRun.characterRun.characterIndex && + characterIndex < (tempParagraphRun.characterRun.characterIndex + tempParagraphRun.characterRun.numberOfCharacters)) + { + currentParagraphRun = tempParagraphRun; + return true; + } + } + + return false; + } + + bool LayoutText(Parameters& layoutParameters, + Size& layoutSize, + bool elideTextEnabled, + bool& isAutoScrollEnabled, + bool isAutoScrollMaxTextureExceeded, + DevelText::EllipsisPosition::Type ellipsisPosition) + { + DALI_LOG_INFO(gLogFilter, Debug::Verbose, "-->LayoutText\n"); + DALI_LOG_INFO(gLogFilter, Debug::Verbose, " box size %f, %f\n", layoutParameters.boundingBox.width, layoutParameters.boundingBox.height); + + layoutParameters.textModel->mVisualModel->mHyphen.glyph.Clear(); + layoutParameters.textModel->mVisualModel->mHyphen.index.Clear(); + + //Reset indices of ElidedGlyphs + layoutParameters.textModel->mVisualModel->SetStartIndexOfElidedGlyphs(0u); + layoutParameters.textModel->mVisualModel->SetEndIndexOfElidedGlyphs(layoutParameters.textModel->GetNumberOfGlyphs() - 1u); + layoutParameters.textModel->mVisualModel->SetFirstMiddleIndexOfElidedGlyphs(0u); + layoutParameters.textModel->mVisualModel->SetSecondMiddleIndexOfElidedGlyphs(0u); + + Vector& lines = layoutParameters.textModel->mVisualModel->mLines; + const Vector& boundedParagraphRuns = layoutParameters.textModel->GetBoundedParagraphRuns(); + + if(0u == layoutParameters.numberOfGlyphs) { // Add an extra line if the last character is a new paragraph character and the last line doesn't have zero characters. - if( layoutParameters.isLastNewParagraph ) + if(layoutParameters.isLastNewParagraph) { Length numberOfLines = lines.Count(); - if( 0u != numberOfLines ) + if(0u != numberOfLines) { - const LineRun& lastLine = *( lines.End() - 1u ); + const LineRun& lastLine = *(lines.End() - 1u); - if( 0u != lastLine.characterRun.numberOfCharacters ) + if(0u != lastLine.characterRun.numberOfCharacters) { // Need to add a new line with no characters but with height to increase the layoutSize.height LineRun newLine; - Initialize( newLine ); - lines.PushBack( newLine ); - - UpdateTextLayout( layoutParameters, - lastLine.characterRun.characterIndex + lastLine.characterRun.numberOfCharacters, - lastLine.glyphRun.glyphIndex + lastLine.glyphRun.numberOfGlyphs, - layoutSize, - lines.Begin(), - numberOfLines ); + Initialize(newLine); + lines.PushBack(newLine); + + UpdateTextLayout(layoutParameters, + lastLine.characterRun.characterIndex + lastLine.characterRun.numberOfCharacters, + lastLine.glyphRun.glyphIndex + lastLine.glyphRun.numberOfGlyphs, + layoutSize, + lines.Begin(), + numberOfLines); } } } // Calculates the layout size. - UpdateLayoutSize( lines, - layoutSize ); + UpdateLayoutSize(lines, + layoutSize); // Rounds upward to avoid a non integer size. - layoutSize.height = std::ceil( layoutSize.height ); + layoutSize.height = std::ceil(layoutSize.height); // Nothing else do if there are no glyphs to layout. return false; } - const GlyphIndex lastGlyphPlusOne = layoutParameters.startGlyphIndex + layoutParameters.numberOfGlyphs; + const GlyphIndex lastGlyphPlusOne = layoutParameters.startGlyphIndex + layoutParameters.numberOfGlyphs; + const Length totalNumberOfGlyphs = layoutParameters.textModel->mVisualModel->mGlyphs.Count(); + Vector& glyphPositions = layoutParameters.textModel->mVisualModel->mGlyphPositions; // In a previous layout, an extra line with no characters may have been added if the text ended with a new paragraph character. // This extra line needs to be removed. - if( 0u != lines.Count() ) + if(0u != lines.Count()) { Vector::Iterator lastLine = lines.End() - 1u; - if( ( 0u == lastLine->characterRun.numberOfCharacters ) && - ( lastGlyphPlusOne == layoutParameters.totalNumberOfGlyphs ) ) + if((0u == lastLine->characterRun.numberOfCharacters) && + (lastGlyphPlusOne == totalNumberOfGlyphs)) { - lines.Remove( lastLine ); + lines.Remove(lastLine); } } - // Set the first paragraph's direction. - CharacterDirection paragraphDirection = ( NULL != layoutParameters.characterDirectionBuffer ) ? *layoutParameters.characterDirectionBuffer : !RTL; + // Retrieve BiDi info. + const bool hasBidiParagraphs = !layoutParameters.textModel->mLogicalModel->mBidirectionalParagraphInfo.Empty(); + + const CharacterIndex* const glyphsToCharactersBuffer = layoutParameters.textModel->mVisualModel->mGlyphsToCharacters.Begin(); + const Vector& bidirectionalParagraphsInfo = layoutParameters.textModel->mLogicalModel->mBidirectionalParagraphInfo; + const Vector& bidirectionalLinesInfo = layoutParameters.textModel->mLogicalModel->mBidirectionalLineInfo; + + // Set the layout bidirectional paramters. + LayoutBidiParameters layoutBidiParameters; // Whether the layout is being updated or set from scratch. - const bool updateCurrentBuffer = layoutParameters.numberOfGlyphs < layoutParameters.totalNumberOfGlyphs; + const bool updateCurrentBuffer = layoutParameters.numberOfGlyphs < totalNumberOfGlyphs; - Vector2* glyphPositionsBuffer = NULL; + Vector2* glyphPositionsBuffer = nullptr; Vector newGlyphPositions; - LineRun* linesBuffer = NULL; + LineRun* linesBuffer = nullptr; Vector newLines; // Estimate the number of lines. - Length linesCapacity = std::max( 1u, layoutParameters.estimatedNumberOfLines ); + Length linesCapacity = std::max(1u, layoutParameters.estimatedNumberOfLines); Length numberOfLines = 0u; - if( updateCurrentBuffer ) + if(updateCurrentBuffer) { - newGlyphPositions.Resize( layoutParameters.numberOfGlyphs ); + newGlyphPositions.Resize(layoutParameters.numberOfGlyphs); glyphPositionsBuffer = newGlyphPositions.Begin(); - newLines.Resize( linesCapacity ); + newLines.Resize(linesCapacity); linesBuffer = newLines.Begin(); } else { glyphPositionsBuffer = glyphPositions.Begin(); - lines.Resize( linesCapacity ); + lines.Resize(linesCapacity); linesBuffer = lines.Begin(); } - float penY = CalculateLineOffset( lines, - layoutParameters.startLineIndex ); - - for( GlyphIndex index = layoutParameters.startGlyphIndex; index < lastGlyphPlusOne; ) + float penY = CalculateLineOffset(lines, + layoutParameters.startLineIndex); + bool anyLineIsEliped = false; + for(GlyphIndex index = layoutParameters.startGlyphIndex; index < lastGlyphPlusOne;) { - CharacterDirection currentParagraphDirection = paragraphDirection; + layoutBidiParameters.Clear(); + + if(hasBidiParagraphs) + { + const CharacterIndex startCharacterIndex = *(glyphsToCharactersBuffer + index); + + for(Vector::ConstIterator it = bidirectionalParagraphsInfo.Begin(), + endIt = bidirectionalParagraphsInfo.End(); + it != endIt; + ++it, ++layoutBidiParameters.bidiParagraphIndex) + { + const BidirectionalParagraphInfoRun& run = *it; + + const CharacterIndex lastCharacterIndex = run.characterRun.characterIndex + run.characterRun.numberOfCharacters; + + if(lastCharacterIndex <= startCharacterIndex) + { + // Do not process, the paragraph has already been processed. + continue; + } + + if(startCharacterIndex >= run.characterRun.characterIndex && startCharacterIndex < lastCharacterIndex) + { + layoutBidiParameters.paragraphDirection = run.direction; + layoutBidiParameters.isBidirectional = true; + } + + // Has already been found. + break; + } + + if(layoutBidiParameters.isBidirectional) + { + for(Vector::ConstIterator it = bidirectionalLinesInfo.Begin(), + endIt = bidirectionalLinesInfo.End(); + it != endIt; + ++it, ++layoutBidiParameters.bidiLineIndex) + { + const BidirectionalLineInfoRun& run = *it; + + const CharacterIndex lastCharacterIndex = run.characterRun.characterIndex + run.characterRun.numberOfCharacters; + + if(lastCharacterIndex <= startCharacterIndex) + { + // skip + continue; + } + + if(startCharacterIndex < lastCharacterIndex) + { + // Found where to insert the bidi line info. + break; + } + } + } + } + + CharacterDirection currentParagraphDirection = layoutBidiParameters.paragraphDirection; // Get the layout for the line. LineLayout layout; + layout.direction = layoutBidiParameters.paragraphDirection; layout.glyphIndex = index; - GetLineLayoutForBox( layoutParameters, - layout, - paragraphDirection, - false ); - - DALI_LOG_INFO( gLogFilter, Debug::Verbose, " glyph index %d\n", layout.glyphIndex ); - DALI_LOG_INFO( gLogFilter, Debug::Verbose, " character index %d\n", layout.characterIndex ); - DALI_LOG_INFO( gLogFilter, Debug::Verbose, " number of glyphs %d\n", layout.numberOfGlyphs ); - DALI_LOG_INFO( gLogFilter, Debug::Verbose, " number of characters %d\n", layout.numberOfCharacters ); - DALI_LOG_INFO( gLogFilter, Debug::Verbose, " length %f\n", layout.length ); - - if( 0u == layout.numberOfGlyphs ) + + BoundedParagraphRun currentParagraphRun; + (GetBoundedParagraph(boundedParagraphRuns, *(glyphsToCharactersBuffer + index), currentParagraphRun) ? SetRelativeLineSize(¤tParagraphRun, layout) : SetRelativeLineSize(nullptr, layout)); + + GetLineLayoutForBox(layoutParameters, + layoutBidiParameters, + layout, + false, + ellipsisPosition, + false, + elideTextEnabled); + + DALI_LOG_INFO(gLogFilter, Debug::Verbose, " glyph index %d\n", layout.glyphIndex); + DALI_LOG_INFO(gLogFilter, Debug::Verbose, " character index %d\n", layout.characterIndex); + DALI_LOG_INFO(gLogFilter, Debug::Verbose, " number of glyphs %d\n", layout.numberOfGlyphs); + DALI_LOG_INFO(gLogFilter, Debug::Verbose, " number of characters %d\n", layout.numberOfCharacters); + DALI_LOG_INFO(gLogFilter, Debug::Verbose, " length %f\n", layout.length); + + CharacterIndex lastCharacterInParagraph = currentParagraphRun.characterRun.characterIndex + currentParagraphRun.characterRun.numberOfCharacters - 1; + + //check if this is the last line in paragraph, if false we should use the default relative line size (the one set using the property) + if(lastCharacterInParagraph >= layout.characterIndex && lastCharacterInParagraph < layout.characterIndex + layout.numberOfCharacters) + { + layout.relativeLineSize = mRelativeLineSize; + } + + if(0u == layout.numberOfGlyphs + layout.numberOfGlyphsInSecondHalfLine) { // The width is too small and no characters are laid-out. - DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--LayoutText width too small!\n\n" ); + DALI_LOG_INFO(gLogFilter, Debug::Verbose, "<--LayoutText width too small!\n\n"); - lines.Resize( numberOfLines ); + lines.Resize(numberOfLines); // Rounds upward to avoid a non integer size. - layoutSize.height = std::ceil( layoutSize.height ); + layoutSize.height = std::ceil(layoutSize.height); return false; } - // Set the line position. Discard if ellipsis is enabled and the position exceeds the boundaries + // Set the line position. DISCARD if ellipsis is enabled and the position exceeds the boundaries // of the box. penY += layout.ascender; - DALI_LOG_INFO( gLogFilter, Debug::Verbose, " pen y %f\n", penY ); + DALI_LOG_INFO(gLogFilter, Debug::Verbose, " pen y %f\n", penY); bool ellipsis = false; - if( elideTextEnabled ) + if(elideTextEnabled) { + layoutBidiParameters.paragraphDirection = currentParagraphDirection; + // Does the ellipsis of the last line. - ellipsis = EllipsisLine( layoutParameters, - layout, - layoutSize, - linesBuffer, - glyphPositionsBuffer, - numberOfLines, - penY, - currentParagraphDirection, - isAutoScrollEnabled ); + ellipsis = EllipsisLine(layoutParameters, + layoutBidiParameters, + layout, + layoutSize, + linesBuffer, + glyphPositionsBuffer, + numberOfLines, + penY, + isAutoScrollEnabled, + isAutoScrollMaxTextureExceeded, + ellipsisPosition, + false); } - if( ellipsis ) + if(ellipsis && ((ellipsisPosition == DevelText::EllipsisPosition::END) || (numberOfLines == 1u))) { + const bool isMultiline = mLayout == MULTI_LINE_BOX; + if(isMultiline && ellipsisPosition != DevelText::EllipsisPosition::END) + { + ellipsis = EllipsisLine(layoutParameters, + layoutBidiParameters, + layout, + layoutSize, + linesBuffer, + glyphPositionsBuffer, + numberOfLines, + penY, + isAutoScrollEnabled, + isAutoScrollMaxTextureExceeded, + ellipsisPosition, + true); + } + + //clear hyphen from ellipsis line + const Length* hyphenIndices = layoutParameters.textModel->mVisualModel->mHyphen.index.Begin(); + Length hyphensCount = layoutParameters.textModel->mVisualModel->mHyphen.glyph.Size(); + + while(hyphenIndices && hyphensCount > 0 && hyphenIndices[hyphensCount - 1] >= layout.glyphIndex) + { + layoutParameters.textModel->mVisualModel->mHyphen.index.Remove(layoutParameters.textModel->mVisualModel->mHyphen.index.Begin() + hyphensCount - 1); + layoutParameters.textModel->mVisualModel->mHyphen.glyph.Remove(layoutParameters.textModel->mVisualModel->mHyphen.glyph.Begin() + hyphensCount - 1); + hyphensCount--; + } + // No more lines to layout. break; } else { + //In START location of ellipsis whether to shift lines or not. + anyLineIsEliped |= ellipsis; + // Whether the last line has been laid-out. - const bool isLastLine = index + layout.numberOfGlyphs == layoutParameters.totalNumberOfGlyphs; + const bool isLastLine = index + (layout.numberOfGlyphs + layout.numberOfGlyphsInSecondHalfLine) == totalNumberOfGlyphs; - if( numberOfLines == linesCapacity ) + if(numberOfLines == linesCapacity) { // Reserve more space for the next lines. - linesBuffer = ResizeLinesBuffer( lines, - newLines, - linesCapacity, - updateCurrentBuffer ); + linesBuffer = ResizeLinesBuffer(lines, + newLines, + linesCapacity, + updateCurrentBuffer); } // Updates the current text's layout with the line's layout. - UpdateTextLayout( layoutParameters, - layout, - layoutSize, - linesBuffer, - index, - numberOfLines, - isLastLine ); - - const GlyphIndex nextIndex = index + layout.numberOfGlyphs; - - if( ( nextIndex == layoutParameters.totalNumberOfGlyphs ) && - layoutParameters.isLastNewParagraph && - ( mLayout == MULTI_LINE_BOX ) ) + UpdateTextLayout(layoutParameters, + layout, + layoutSize, + linesBuffer, + index, + numberOfLines, + isLastLine); + + const GlyphIndex nextIndex = index + layout.numberOfGlyphs + layout.numberOfGlyphsInSecondHalfLine; + + if((nextIndex == totalNumberOfGlyphs) && + layoutParameters.isLastNewParagraph && + (mLayout == MULTI_LINE_BOX)) { // The last character of the text is a new paragraph character. // An extra line with no characters is added to increase the text's height // in order to place the cursor. - if( numberOfLines == linesCapacity ) + if(numberOfLines == linesCapacity) { // Reserve more space for the next lines. - linesBuffer = ResizeLinesBuffer( lines, - newLines, - linesCapacity, - updateCurrentBuffer ); + linesBuffer = ResizeLinesBuffer(lines, + newLines, + linesCapacity, + updateCurrentBuffer); } - UpdateTextLayout( layoutParameters, - layout.characterIndex + layout.numberOfCharacters, - index + layout.numberOfGlyphs, - layoutSize, - linesBuffer, - numberOfLines ); + UpdateTextLayout(layoutParameters, + layout.characterIndex + (layout.numberOfCharacters + layout.numberOfCharactersInSecondHalfLine), + index + (layout.numberOfGlyphs + layout.numberOfGlyphsInSecondHalfLine), + layoutSize, + linesBuffer, + numberOfLines); } // whether to add a last line. - // Sets the positions of the glyphs. - SetGlyphPositions( layoutParameters.glyphsBuffer + index, - layout.numberOfGlyphs, - layoutParameters.outlineWidth, - layoutParameters.interGlyphExtraAdvance, - glyphPositionsBuffer + index - layoutParameters.startGlyphIndex ); + const BidirectionalLineInfoRun* const bidirectionalLineInfo = (layoutBidiParameters.isBidirectional && !bidirectionalLinesInfo.Empty()) ? &bidirectionalLinesInfo[layoutBidiParameters.bidiLineIndex] : nullptr; + + if((nullptr != bidirectionalLineInfo) && + !bidirectionalLineInfo->isIdentity && + (layout.characterIndex == bidirectionalLineInfo->characterRun.characterIndex)) + { + SetGlyphPositions(layoutParameters, + glyphPositionsBuffer, + layoutBidiParameters, + layout); + } + else + { + // Sets the positions of the glyphs. + SetGlyphPositions(layoutParameters, + glyphPositionsBuffer, + layout); + } // Updates the vertical pen's position. - penY += -layout.descender + layout.lineSpacing + mDefaultLineSpacing; + penY += -layout.descender + layout.lineSpacing + GetLineSpacing(layout.ascender + -layout.descender, layout.relativeLineSize); // Increase the glyph index. index = nextIndex; } // no ellipsis - } // end for() traversing glyphs. + } // end for() traversing glyphs. - if( updateCurrentBuffer ) + //Shift lines to up if ellipsis and multilines and set ellipsis of first line to true + if(anyLineIsEliped && numberOfLines > 1u) { - glyphPositions.Insert( glyphPositions.Begin() + layoutParameters.startGlyphIndex, - newGlyphPositions.Begin(), - newGlyphPositions.End() ); - glyphPositions.Resize( layoutParameters.totalNumberOfGlyphs ); + if(ellipsisPosition == DevelText::EllipsisPosition::START) + { + Length lineIndex = 0; + while(lineIndex < numberOfLines && layoutParameters.boundingBox.height < layoutSize.height) + { + LineRun& delLine = linesBuffer[lineIndex]; + delLine.ellipsis = true; - newLines.Resize( numberOfLines ); + layoutSize.height -= (delLine.ascender + -delLine.descender) + delLine.lineSpacing; + for(Length lineIndex = 0; lineIndex < numberOfLines - 1; lineIndex++) + { + linesBuffer[lineIndex] = linesBuffer[lineIndex + 1]; + linesBuffer[lineIndex].ellipsis = false; + } + numberOfLines--; + } + linesBuffer[0u].ellipsis = true; + } + else if(ellipsisPosition == DevelText::EllipsisPosition::MIDDLE) + { + Length middleLineIndex = (numberOfLines) / 2u; + Length ellipsisLineIndex = 0u; + while(1u < numberOfLines && 0u < middleLineIndex && layoutParameters.boundingBox.height < layoutSize.height) + { + LineRun& delLine = linesBuffer[middleLineIndex]; + delLine.ellipsis = true; + + layoutSize.height -= (delLine.ascender + -delLine.descender) + delLine.lineSpacing; + for(Length lineIndex = middleLineIndex; lineIndex < numberOfLines - 1; lineIndex++) + { + linesBuffer[lineIndex] = linesBuffer[lineIndex + 1]; + linesBuffer[lineIndex].ellipsis = false; + } + numberOfLines--; + ellipsisLineIndex = middleLineIndex - 1u; + middleLineIndex = (numberOfLines) / 2u; + } + + linesBuffer[ellipsisLineIndex].ellipsis = true; + } + } + + if(updateCurrentBuffer) + { + glyphPositions.Insert(glyphPositions.Begin() + layoutParameters.startGlyphIndex, + newGlyphPositions.Begin(), + newGlyphPositions.End()); + glyphPositions.Resize(totalNumberOfGlyphs); + + newLines.Resize(numberOfLines); // Current text's layout size adds only the newly laid-out lines. // Updates the layout size with the previously laid-out lines. - UpdateLayoutSize( lines, - layoutSize ); + UpdateLayoutSize(lines, + layoutSize); - if( 0u != newLines.Count() ) + if(0u != newLines.Count()) { - const LineRun& lastLine = *( newLines.End() - 1u ); + const LineRun& lastLine = *(newLines.End() - 1u); const Length characterOffset = lastLine.characterRun.characterIndex + lastLine.characterRun.numberOfCharacters; - const Length glyphOffset = lastLine.glyphRun.glyphIndex + lastLine.glyphRun.numberOfGlyphs; + const Length glyphOffset = lastLine.glyphRun.glyphIndex + lastLine.glyphRun.numberOfGlyphs; // Update the indices of the runs before the new laid-out lines are inserted. - UpdateLineIndexOffsets( layoutParameters, - lines, - characterOffset, - glyphOffset ); + UpdateLineIndexOffsets(layoutParameters, + lines, + characterOffset, + glyphOffset); // Insert the lines. - lines.Insert( lines.Begin() + layoutParameters.startLineIndex, - newLines.Begin(), - newLines.End() ); + lines.Insert(lines.Begin() + layoutParameters.startLineIndex, + newLines.Begin(), + newLines.End()); } } else { - lines.Resize( numberOfLines ); + lines.Resize(numberOfLines); } // Rounds upward to avoid a non integer size. - layoutSize.height = std::ceil( layoutSize.height ); + layoutSize.height = std::ceil(layoutSize.height); - DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--LayoutText\n\n" ); + DALI_LOG_INFO(gLogFilter, Debug::Verbose, "<--LayoutText\n\n"); return true; } - void ReLayoutRightToLeftLines( const Parameters& layoutParameters, - CharacterIndex startIndex, - Length numberOfCharacters, - Vector& glyphPositions ) - { - const CharacterIndex lastCharacterIndex = startIndex + numberOfCharacters; - - // Traverses the paragraphs with right to left characters. - for( LineIndex lineIndex = 0u; lineIndex < layoutParameters.numberOfBidirectionalInfoRuns; ++lineIndex ) - { - const BidirectionalLineInfoRun& bidiLine = *( layoutParameters.lineBidirectionalInfoRunsBuffer + lineIndex ); - - if( startIndex >= bidiLine.characterRun.characterIndex + bidiLine.characterRun.numberOfCharacters ) - { - // Do not reorder the line if it has been already reordered. - continue; - } - - if( bidiLine.characterRun.characterIndex >= lastCharacterIndex ) - { - // Do not reorder the lines after the last requested character. - break; - } - - const CharacterIndex characterVisualIndex = bidiLine.characterRun.characterIndex + *bidiLine.visualToLogicalMap; - const GlyphInfo& glyph = *( layoutParameters.glyphsBuffer + *( layoutParameters.charactersToGlyphsBuffer + characterVisualIndex ) ); - - float penX = -glyph.xBearing + layoutParameters.outlineWidth + mCursorWidth; - - Vector2* glyphPositionsBuffer = glyphPositions.Begin(); - - // Traverses the characters of the right to left paragraph. - for( CharacterIndex characterLogicalIndex = 0u; - characterLogicalIndex < bidiLine.characterRun.numberOfCharacters; - ++characterLogicalIndex ) - { - // Convert the character in the logical order into the character in the visual order. - const CharacterIndex characterVisualIndex = bidiLine.characterRun.characterIndex + *( bidiLine.visualToLogicalMap + characterLogicalIndex ); - - // Get the number of glyphs of the character. - const Length numberOfGlyphs = *( layoutParameters.glyphsPerCharacterBuffer + characterVisualIndex ); - - for( GlyphIndex index = 0u; index < numberOfGlyphs; ++index ) - { - // Convert the character in the visual order into the glyph in the visual order. - const GlyphIndex glyphIndex = *( layoutParameters.charactersToGlyphsBuffer + characterVisualIndex ) + index; - - DALI_ASSERT_DEBUG( 0u <= glyphIndex && glyphIndex < layoutParameters.totalNumberOfGlyphs ); - - const GlyphInfo& glyph = *( layoutParameters.glyphsBuffer + glyphIndex ); - Vector2& position = *( glyphPositionsBuffer + glyphIndex ); - - position.x = std::round( penX + glyph.xBearing ); - penX += ( glyph.advance + layoutParameters.interGlyphExtraAdvance ); - } - } - } - } - - void Align( const Size& size, - CharacterIndex startIndex, - Length numberOfCharacters, - Text::HorizontalAlignment::Type horizontalAlignment, - Vector& lines, - float& alignmentOffset, - Dali::LayoutDirection::Type layoutDirection, - bool matchSystemLanguageDirection ) + void Align(const Size& size, + CharacterIndex startIndex, + Length numberOfCharacters, + Text::HorizontalAlignment::Type horizontalAlignment, + Vector& lines, + float& alignmentOffset, + Dali::LayoutDirection::Type layoutDirection, + bool matchLayoutDirection) { const CharacterIndex lastCharacterPlusOne = startIndex + numberOfCharacters; alignmentOffset = MAX_FLOAT; // Traverse all lines and align the glyphs. - for( Vector::Iterator it = lines.Begin(), endIt = lines.End(); - it != endIt; - ++it ) + for(Vector::Iterator it = lines.Begin(), endIt = lines.End(); + it != endIt; + ++it) { LineRun& line = *it; - if( line.characterRun.characterIndex < startIndex ) + if(line.characterRun.characterIndex < startIndex) { // Do not align lines which have already been aligned. continue; } - if( line.characterRun.characterIndex > lastCharacterPlusOne ) + if(line.characterRun.characterIndex > lastCharacterPlusOne) { // Do not align lines beyond the last laid-out character. break; } - if( line.characterRun.characterIndex == lastCharacterPlusOne && !isEmptyLineAtLast( lines, it ) ) + if(line.characterRun.characterIndex == lastCharacterPlusOne && !isEmptyLineAtLast(lines, it)) { // Do not align lines beyond the last laid-out character unless the line is last and empty. break; @@ -1061,44 +2095,45 @@ struct Engine::Impl // Calculate the line's alignment offset accordingly with the align option, // the box width, line length, and the paragraph's direction. - CalculateHorizontalAlignment( size.width, - horizontalAlignment, - line, - layoutDirection, - matchSystemLanguageDirection ); + CalculateHorizontalAlignment(size.width, + horizontalAlignment, + line, + layoutDirection, + matchLayoutDirection); // Updates the alignment offset. - alignmentOffset = std::min( alignmentOffset, line.alignmentOffset ); + alignmentOffset = std::min(alignmentOffset, line.alignmentOffset); } } - void CalculateHorizontalAlignment( float boxWidth, - HorizontalAlignment::Type horizontalAlignment, - LineRun& line, - Dali::LayoutDirection::Type layoutDirection, - bool matchSystemLanguageDirection ) + void CalculateHorizontalAlignment(float boxWidth, + HorizontalAlignment::Type horizontalAlignment, + LineRun& line, + Dali::LayoutDirection::Type layoutDirection, + bool matchLayoutDirection) { line.alignmentOffset = 0.f; const bool isLineRTL = RTL == line.direction; + // Whether to swap the alignment. // Swap if the line is RTL and is not required to match the direction of the system's language or if it's required to match the direction of the system's language and it's RTL. - bool isLayoutRTL = isLineRTL; - float lineLength = line.width; + bool isLayoutRTL = isLineRTL; + float lineLength = line.width; // match align for system language direction - if( matchSystemLanguageDirection ) + if(matchLayoutDirection) { // Swap the alignment type if the line is right to left. isLayoutRTL = layoutDirection == LayoutDirection::RIGHT_TO_LEFT; } // Calculate the horizontal line offset. - switch( horizontalAlignment ) + switch(horizontalAlignment) { case HorizontalAlignment::BEGIN: { - if( isLayoutRTL ) + if(isLayoutRTL) { - if( isLineRTL ) + if(isLineRTL) { lineLength += line.extraLength; } @@ -1109,7 +2144,7 @@ struct Engine::Impl { line.alignmentOffset = 0.f; - if( isLineRTL ) + if(isLineRTL) { // 'Remove' the white spaces at the end of the line (which are at the beginning in visual order) line.alignmentOffset -= line.extraLength; @@ -1119,23 +2154,23 @@ struct Engine::Impl } case HorizontalAlignment::CENTER: { - line.alignmentOffset = 0.5f * ( boxWidth - lineLength ); + line.alignmentOffset = 0.5f * (boxWidth - lineLength); - if( isLineRTL ) + if(isLineRTL) { line.alignmentOffset -= line.extraLength; } - line.alignmentOffset = std::floor( line.alignmentOffset ); // floor() avoids pixel alignment issues. + line.alignmentOffset = std::floor(line.alignmentOffset); // floor() avoids pixel alignment issues. break; } case HorizontalAlignment::END: { - if( isLayoutRTL ) + if(isLayoutRTL) { line.alignmentOffset = 0.f; - if( isLineRTL ) + if(isLineRTL) { // 'Remove' the white spaces at the end of the line (which are at the beginning in visual order) line.alignmentOffset -= line.extraLength; @@ -1143,7 +2178,7 @@ struct Engine::Impl } else { - if( isLineRTL ) + if(isLineRTL) { lineLength += line.extraLength; } @@ -1155,31 +2190,38 @@ struct Engine::Impl } } - void Initialize( LineRun& line ) + void Initialize(LineRun& line) { - line.glyphRun.glyphIndex = 0u; - line.glyphRun.numberOfGlyphs = 0u; - line.characterRun.characterIndex = 0u; - line.characterRun.numberOfCharacters = 0u; - line.width = 0.f; - line.ascender = 0.f; - line.descender = 0.f; - line.extraLength = 0.f; - line.alignmentOffset = 0.f; - line.direction = !RTL; - line.ellipsis = false; - line.lineSpacing = mDefaultLineSpacing; + line.glyphRun.glyphIndex = 0u; + line.glyphRun.numberOfGlyphs = 0u; + line.characterRun.characterIndex = 0u; + line.characterRun.numberOfCharacters = 0u; + line.width = 0.f; + line.ascender = 0.f; + line.descender = 0.f; + line.extraLength = 0.f; + line.alignmentOffset = 0.f; + line.direction = LTR; + line.ellipsis = false; + line.lineSpacing = mDefaultLineSpacing; + line.isSplitToTwoHalves = false; + line.glyphRunSecondHalf.glyphIndex = 0u; + line.glyphRunSecondHalf.numberOfGlyphs = 0u; + line.characterRunForSecondHalfLine.characterIndex = 0u; + line.characterRunForSecondHalfLine.numberOfCharacters = 0u; } - Type mLayout; + Type mLayout; float mCursorWidth; float mDefaultLineSpacing; + float mDefaultLineSize; IntrusivePtr mMetrics; + float mRelativeLineSize; }; Engine::Engine() -: mImpl( NULL ) +: mImpl{nullptr} { mImpl = new Engine::Impl(); } @@ -1189,78 +2231,67 @@ Engine::~Engine() delete mImpl; } -void Engine::SetMetrics( MetricsPtr& metrics ) +void Engine::SetMetrics(MetricsPtr& metrics) { mImpl->mMetrics = metrics; } -void Engine::SetLayout( Type layout ) +void Engine::SetLayout(Type layout) { mImpl->mLayout = layout; } Engine::Type Engine::GetLayout() const { - DALI_LOG_INFO( gLogFilter, Debug::Verbose, "GetLayout[%d]\n", mImpl->mLayout); + DALI_LOG_INFO(gLogFilter, Debug::Verbose, "GetLayout[%d]\n", mImpl->mLayout); return mImpl->mLayout; } -void Engine::SetCursorWidth( int width ) +void Engine::SetCursorWidth(int width) { - mImpl->mCursorWidth = static_cast( width ); + mImpl->mCursorWidth = static_cast(width); } int Engine::GetCursorWidth() const { - return static_cast( mImpl->mCursorWidth ); -} - -bool Engine::LayoutText( const Parameters& layoutParameters, - Vector& glyphPositions, - Vector& lines, - Size& layoutSize, - bool elideTextEnabled, - bool& isAutoScrollEnabled ) -{ - return mImpl->LayoutText( layoutParameters, - glyphPositions, - lines, - layoutSize, - elideTextEnabled, - isAutoScrollEnabled ); + return static_cast(mImpl->mCursorWidth); } -void Engine::ReLayoutRightToLeftLines( const Parameters& layoutParameters, - CharacterIndex startIndex, - Length numberOfCharacters, - Vector& glyphPositions ) +bool Engine::LayoutText(Parameters& layoutParameters, + Size& layoutSize, + bool elideTextEnabled, + bool& isAutoScrollEnabled, + bool isAutoScrollMaxTextureExceeded, + DevelText::EllipsisPosition::Type ellipsisPosition) { - mImpl->ReLayoutRightToLeftLines( layoutParameters, - startIndex, - numberOfCharacters, - glyphPositions ); + return mImpl->LayoutText(layoutParameters, + layoutSize, + elideTextEnabled, + isAutoScrollEnabled, + isAutoScrollMaxTextureExceeded, + ellipsisPosition); } -void Engine::Align( const Size& size, - CharacterIndex startIndex, - Length numberOfCharacters, - Text::HorizontalAlignment::Type horizontalAlignment, - Vector& lines, - float& alignmentOffset, - Dali::LayoutDirection::Type layoutDirection, - bool matchSystemLanguageDirection ) +void Engine::Align(const Size& size, + CharacterIndex startIndex, + Length numberOfCharacters, + Text::HorizontalAlignment::Type horizontalAlignment, + Vector& lines, + float& alignmentOffset, + Dali::LayoutDirection::Type layoutDirection, + bool matchLayoutDirection) { - mImpl->Align( size, - startIndex, - numberOfCharacters, - horizontalAlignment, - lines, - alignmentOffset, - layoutDirection, - matchSystemLanguageDirection ); + mImpl->Align(size, + startIndex, + numberOfCharacters, + horizontalAlignment, + lines, + alignmentOffset, + layoutDirection, + matchLayoutDirection); } -void Engine::SetDefaultLineSpacing( float lineSpacing ) +void Engine::SetDefaultLineSpacing(float lineSpacing) { mImpl->mDefaultLineSpacing = lineSpacing; } @@ -1270,6 +2301,26 @@ float Engine::GetDefaultLineSpacing() const return mImpl->mDefaultLineSpacing; } +void Engine::SetDefaultLineSize(float lineSize) +{ + mImpl->mDefaultLineSize = lineSize; +} + +float Engine::GetDefaultLineSize() const +{ + return mImpl->mDefaultLineSize; +} + +void Engine::SetRelativeLineSize(float relativeLineSize) +{ + mImpl->mRelativeLineSize = relativeLineSize; +} + +float Engine::GetRelativeLineSize() const +{ + return mImpl->mRelativeLineSize; +} + } // namespace Layout } // namespace Text