2 * Copyright (c) 2017 Samsung Electronics Co., Ltd.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
19 #include <dali-toolkit/internal/text/layouts/layout-engine.h>
24 #include <dali/integration-api/debug.h>
25 #include <dali/devel-api/text-abstraction/font-client.h>
28 #include <dali-toolkit/internal/text/bidirectional-line-info-run.h>
29 #include <dali-toolkit/internal/text/cursor-helper-functions.h>
30 #include <dali-toolkit/internal/text/glyph-metrics-helper.h>
31 #include <dali-toolkit/internal/text/layouts/layout-parameters.h>
48 #if defined(DEBUG_ENABLED)
49 Debug::Filter* gLogFilter = Debug::Filter::New(Debug::Concise, true, "LOG_TEXT_LAYOUT");
52 const float MAX_FLOAT = std::numeric_limits<float>::max();
53 const CharacterDirection LTR = false;
54 const CharacterDirection RTL = !LTR;
55 const float LINE_SPACING= 0.f;
57 inline bool isEmptyLineAtLast( const Vector<LineRun>& lines, const Vector<LineRun>::Iterator& line )
59 return ( (*line).characterRun.numberOfCharacters == 0 && line + 1u == lines.End() );
65 * @brief Stores temporary layout info of the line.
73 numberOfCharacters( 0u ),
74 ascender( -MAX_FLOAT ),
75 descender( MAX_FLOAT ),
78 previousAdvance( 0.f ),
80 wsLengthEndOfLine( 0.f )
91 numberOfCharacters = 0u;
93 descender = MAX_FLOAT;
96 GlyphIndex glyphIndex; ///< Index of the first glyph to be laid-out.
97 CharacterIndex characterIndex; ///< Index of the first character to be laid-out.
98 Length numberOfGlyphs; ///< The number of glyph which fit in one line.
99 Length numberOfCharacters; ///< The number of characters which fit in one line.
100 float ascender; ///< The maximum ascender of all fonts in the line.
101 float descender; ///< The minimum descender of all fonts in the line.
102 float lineSpacing; ///< The line spacing
103 float penX; ///< The origin of the current glyph ( is the start point plus the accumulation of all advances ).
104 float previousAdvance; ///< The advance of the previous glyph.
105 float length; ///< The current length of the line.
106 float wsLengthEndOfLine; ///< The length of the white spaces at the end of the line.
112 : mLayout( Layout::Engine::SINGLE_LINE_BOX ),
114 mDefaultLineSpacing( LINE_SPACING )
119 * @brief Updates the line ascender and descender with the metrics of a new font.
121 * @param[in] glyphMetrics The metrics of the new font.
122 * @param[in,out] lineLayout The line layout.
124 void UpdateLineHeight( const GlyphMetrics& glyphMetrics, LineLayout& lineLayout )
126 Text::FontMetrics fontMetrics;
127 if( 0u != glyphMetrics.fontId )
129 mMetrics->GetFontMetrics( glyphMetrics.fontId, fontMetrics );
133 fontMetrics.ascender = glyphMetrics.fontHeight;
134 fontMetrics.descender = 0.f;
135 fontMetrics.height = fontMetrics.ascender;
136 fontMetrics.underlinePosition = 0.f;
137 fontMetrics.underlineThickness = 1.f;
140 // Sets the maximum ascender.
141 lineLayout.ascender = std::max( lineLayout.ascender, fontMetrics.ascender );
143 // Sets the minimum descender.
144 lineLayout.descender = std::min( lineLayout.descender, fontMetrics.descender );
146 // set the line spacing
147 lineLayout.lineSpacing = mDefaultLineSpacing;
151 * @brief Merges a temporary line layout into the line layout.
153 * @param[in,out] lineLayout The line layout.
154 * @param[in] tmpLineLayout A temporary line layout.
156 void MergeLineLayout( LineLayout& lineLayout,
157 const LineLayout& tmpLineLayout )
159 lineLayout.numberOfCharacters += tmpLineLayout.numberOfCharacters;
160 lineLayout.numberOfGlyphs += tmpLineLayout.numberOfGlyphs;
162 lineLayout.penX = tmpLineLayout.penX;
163 lineLayout.previousAdvance = tmpLineLayout.previousAdvance;
165 lineLayout.length = tmpLineLayout.length;
166 lineLayout.wsLengthEndOfLine = tmpLineLayout.wsLengthEndOfLine;
168 // Sets the maximum ascender.
169 lineLayout.ascender = std::max( lineLayout.ascender, tmpLineLayout.ascender );
171 // Sets the minimum descender.
172 lineLayout.descender = std::min( lineLayout.descender, tmpLineLayout.descender );
176 * Retrieves the line layout for a given box width.
178 * @note This method lais out text as it were left to right. At this point is not possible to reorder the line
179 * because the number of characters of the line is not known (one of the responsabilities of this method
180 * is calculate that). Due to glyph's 'x' bearing, width and advance, when right to left or mixed right to left
181 * and left to right text is laid-out, it can be small differences in the line length. One solution is to
182 * reorder and re-lay out the text after this method and add or remove one extra glyph if needed. However,
183 * this method calculates which are the first and last glyphs of the line (the ones that causes the
184 * differences). This is a good point to check if there is problems with the text exceeding the boundaries
185 * of the control when there is right to left text.
187 * @param[in] parameters The layout parameters.
188 * @param[out] lineLayout The line layout.
189 * @param[in,out] paragraphDirection in: the current paragraph's direction, out: the next paragraph's direction. Is set after a must break.
190 * @param[in] completelyFill Whether to completely fill the line ( even if the last word exceeds the boundaries ).
192 void GetLineLayoutForBox( const Parameters& parameters,
193 LineLayout& lineLayout,
194 CharacterDirection& paragraphDirection,
195 bool completelyFill )
197 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "-->GetLineLayoutForBox\n" );
198 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " initial glyph index : %d\n", lineLayout.glyphIndex );
200 const Character* const textBuffer = parameters.textModel->mLogicalModel->mText.Begin();
201 const Length* const charactersPerGlyphBuffer = parameters.textModel->mVisualModel->mCharactersPerGlyph.Begin();
202 const GlyphInfo* const glyphsBuffer = parameters.textModel->mVisualModel->mGlyphs.Begin();
203 const CharacterIndex* const glyphsToCharactersBuffer = parameters.textModel->mVisualModel->mGlyphsToCharacters.Begin();
204 const LineBreakInfo* const lineBreakInfoBuffer = parameters.textModel->mLogicalModel->mLineBreakInfo.Begin();
206 const bool hasBidiParagraphs = !parameters.textModel->mLogicalModel->mBidirectionalParagraphInfo.Empty();
207 const CharacterDirection* const characterDirectionBuffer = hasBidiParagraphs ? parameters.textModel->mLogicalModel->mCharacterDirections.Begin() : nullptr;
209 const float outlineWidth = static_cast<float>( parameters.textModel->GetOutlineWidth() );
210 const Length totalNumberOfGlyphs = parameters.textModel->mVisualModel->mGlyphs.Count();
212 const bool isMultiline = mLayout == MULTI_LINE_BOX;
213 const bool isWordLaidOut = parameters.textModel->mLineWrapMode == Text::LineWrap::WORD;
215 // The last glyph to be laid-out.
216 const GlyphIndex lastGlyphOfParagraphPlusOne = parameters.startGlyphIndex + parameters.numberOfGlyphs;
218 // If the first glyph has a negative bearing its absolute value needs to be added to the line length.
219 // In the case the line starts with a right to left character, if the width is longer than the advance,
220 // the difference needs to be added to the line length.
222 // Check whether the first glyph comes from a character that is shaped in multiple glyphs.
223 const Length numberOfGLyphsInGroup = GetNumberOfGlyphsOfGroup( lineLayout.glyphIndex,
224 lastGlyphOfParagraphPlusOne,
225 charactersPerGlyphBuffer );
227 GlyphMetrics glyphMetrics;
228 GetGlyphsMetrics( lineLayout.glyphIndex,
229 numberOfGLyphsInGroup,
234 // Set the direction of the first character of the line.
235 lineLayout.characterIndex = *( glyphsToCharactersBuffer + lineLayout.glyphIndex );
237 // Stores temporary line layout which has not been added to the final line layout.
238 LineLayout tmpLineLayout;
240 // Initialize the start point.
242 // The initial start point is zero. However it needs a correction according the 'x' bearing of the first glyph.
243 // i.e. if the bearing of the first glyph is negative it may exceed the boundaries of the text area.
244 // 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.
245 tmpLineLayout.penX = -glyphMetrics.xBearing + mCursorWidth + outlineWidth;
247 // Initialize the advance of the previous glyph.
248 tmpLineLayout.previousAdvance = 0.f;
250 // Calculate the line height if there is no characters.
251 FontId lastFontId = glyphMetrics.fontId;
252 UpdateLineHeight( glyphMetrics, tmpLineLayout );
254 bool oneWordLaidOut = false;
256 for( GlyphIndex glyphIndex = lineLayout.glyphIndex;
257 glyphIndex < lastGlyphOfParagraphPlusOne; )
259 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " glyph index : %d\n", glyphIndex );
261 // Check whether this glyph comes from a character that is shaped in multiple glyphs.
262 const Length numberOfGLyphsInGroup = GetNumberOfGlyphsOfGroup( glyphIndex,
263 lastGlyphOfParagraphPlusOne,
264 charactersPerGlyphBuffer );
266 GlyphMetrics glyphMetrics;
267 GetGlyphsMetrics( glyphIndex,
268 numberOfGLyphsInGroup,
273 const bool isLastGlyph = glyphIndex + numberOfGLyphsInGroup == totalNumberOfGlyphs;
275 // Check if the font of the current glyph is the same of the previous one.
276 // If it's different the ascender and descender need to be updated.
277 if( lastFontId != glyphMetrics.fontId )
279 UpdateLineHeight( glyphMetrics, tmpLineLayout );
280 lastFontId = glyphMetrics.fontId;
283 // Get the character indices for the current glyph. The last character index is needed
284 // because there are glyphs formed by more than one character but their break info is
285 // given only for the last character.
286 const Length charactersPerGlyph = *( charactersPerGlyphBuffer + glyphIndex + numberOfGLyphsInGroup - 1u );
287 const bool hasCharacters = charactersPerGlyph > 0u;
288 const CharacterIndex characterFirstIndex = *( glyphsToCharactersBuffer + glyphIndex );
289 const CharacterIndex characterLastIndex = characterFirstIndex + ( hasCharacters ? charactersPerGlyph - 1u : 0u );
291 // Get the line break info for the current character.
292 const LineBreakInfo lineBreakInfo = hasCharacters ? *( lineBreakInfoBuffer + characterLastIndex ) : TextAbstraction::LINE_NO_BREAK;
294 // Increase the number of characters.
295 tmpLineLayout.numberOfCharacters += charactersPerGlyph;
297 // Increase the number of glyphs.
298 tmpLineLayout.numberOfGlyphs += numberOfGLyphsInGroup;
300 // Check whether is a white space.
301 const Character character = *( textBuffer + characterFirstIndex );
302 const bool isWhiteSpace = TextAbstraction::IsWhiteSpace( character );
304 // Calculate the length of the line.
306 // Used to restore the temporal line layout when a single word does not fit in the control's width and is split by character.
307 const float previousTmpPenX = tmpLineLayout.penX;
308 const float previousTmpAdvance = tmpLineLayout.previousAdvance;
309 const float previousTmpLength = tmpLineLayout.length;
310 const float previousTmpWsLengthEndOfLine = tmpLineLayout.wsLengthEndOfLine;
314 // Add the length to the length of white spaces at the end of the line.
315 tmpLineLayout.wsLengthEndOfLine += glyphMetrics.advance; // The advance is used as the width is always zero for the white spaces.
319 tmpLineLayout.penX += tmpLineLayout.previousAdvance + tmpLineLayout.wsLengthEndOfLine;
320 tmpLineLayout.previousAdvance = ( glyphMetrics.advance + parameters.interGlyphExtraAdvance );
321 tmpLineLayout.length = tmpLineLayout.penX + glyphMetrics.xBearing + glyphMetrics.width;
323 // Clear the white space length at the end of the line.
324 tmpLineLayout.wsLengthEndOfLine = 0.f;
327 // Check if the accumulated length fits in the width of the box.
328 if( ( completelyFill || isMultiline ) && !isWhiteSpace &&
329 ( tmpLineLayout.length > parameters.boundingBox.width ) )
331 // Current word does not fit in the box's width.
332 if( !oneWordLaidOut || completelyFill )
334 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " Break the word by character\n" );
336 // The word doesn't fit in the control's width. It needs to be split by character.
337 if( tmpLineLayout.numberOfGlyphs > 0u )
339 tmpLineLayout.numberOfCharacters -= charactersPerGlyph;
340 tmpLineLayout.numberOfGlyphs -= numberOfGLyphsInGroup;
342 tmpLineLayout.penX = previousTmpPenX;
343 tmpLineLayout.previousAdvance = previousTmpAdvance;
344 tmpLineLayout.length = previousTmpLength;
345 tmpLineLayout.wsLengthEndOfLine = previousTmpWsLengthEndOfLine;
348 // Add part of the word to the line layout.
349 MergeLineLayout( lineLayout, tmpLineLayout );
353 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " Current word does not fit.\n" );
356 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--GetLineLayoutForBox.\n" );
361 if( ( isMultiline || isLastGlyph ) &&
362 ( TextAbstraction::LINE_MUST_BREAK == lineBreakInfo ) )
364 // Must break the line. Update the line layout and return.
365 MergeLineLayout( lineLayout, tmpLineLayout );
367 // Set the next paragraph's direction.
369 ( nullptr != characterDirectionBuffer ) )
371 paragraphDirection = *( characterDirectionBuffer + 1u + characterLastIndex );
374 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " Must break\n" );
375 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--GetLineLayoutForBox\n" );
381 ( TextAbstraction::LINE_ALLOW_BREAK == lineBreakInfo ) )
383 oneWordLaidOut = isWordLaidOut;
384 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " One word laid-out\n" );
386 // Current glyph is the last one of the current word.
387 // Add the temporal layout to the current one.
388 MergeLineLayout( lineLayout, tmpLineLayout );
390 tmpLineLayout.Clear();
393 glyphIndex += numberOfGLyphsInGroup;
396 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--GetLineLayoutForBox\n" );
399 void SetGlyphPositions( const GlyphInfo* const glyphsBuffer,
400 Length numberOfGlyphs,
402 float interGlyphExtraAdvance,
403 Vector2* glyphPositionsBuffer )
405 // Traverse the glyphs and set the positions.
407 // Check if the x bearing of the first character is negative.
408 // If it has a negative x bearing, it will exceed the boundaries of the actor,
409 // so the penX position needs to be moved to the right.
411 const GlyphInfo& glyph = *glyphsBuffer;
412 float penX = -glyph.xBearing + mCursorWidth + outlineWidth;
414 for( GlyphIndex i = 0u; i < numberOfGlyphs; ++i )
416 const GlyphInfo& glyph = *( glyphsBuffer + i );
417 Vector2& position = *( glyphPositionsBuffer + i );
419 position.x = std::roundf( penX + glyph.xBearing );
420 position.y = -glyph.yBearing;
422 penX += ( glyph.advance + interGlyphExtraAdvance );
427 * @brief Resizes the line buffer.
429 * @param[in,out] lines The vector of lines. Used when the layout is created from scratch.
430 * @param[in,out] newLines The vector of lines used instead of @p lines when the layout is updated.
431 * @param[in,out] linesCapacity The capacity of the vector (either lines or newLines).
432 * @param[in] updateCurrentBuffer Whether the layout is updated.
434 * @return Pointer to either lines or newLines.
436 LineRun* ResizeLinesBuffer( Vector<LineRun>& lines,
437 Vector<LineRun>& newLines,
438 Length& linesCapacity,
439 bool updateCurrentBuffer )
441 LineRun* linesBuffer = nullptr;
442 // Reserve more space for the next lines.
444 if( updateCurrentBuffer )
446 newLines.Resize( linesCapacity );
447 linesBuffer = newLines.Begin();
451 lines.Resize( linesCapacity );
452 linesBuffer = lines.Begin();
459 * Ellipsis a line if it exceeds the width's of the bounding box.
461 * @param[in] layoutParameters The parameters needed to layout the text.
462 * @param[in] layout The line layout.
463 * @param[in,out] layoutSize The text's layout size.
464 * @param[in,out] linesBuffer Pointer to the line's buffer.
465 * @param[in,out] glyphPositionsBuffer Pointer to the position's buffer.
466 * @param[in,out] numberOfLines The number of laid-out lines.
467 * @param[in] penY The vertical layout position.
468 * @param[in] currentParagraphDirection The current paragraph's direction.
469 * @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
471 * return Whether the line is ellipsized.
473 bool EllipsisLine( const Parameters& layoutParameters,
474 const LineLayout& layout,
476 LineRun* linesBuffer,
477 Vector2* glyphPositionsBuffer,
478 Length& numberOfLines,
480 CharacterDirection currentParagraphDirection,
481 bool& isAutoScrollEnabled )
483 const bool ellipsis = isAutoScrollEnabled ? ( penY - layout.descender > layoutParameters.boundingBox.height ) :
484 ( ( penY - layout.descender > layoutParameters.boundingBox.height ) ||
485 ( ( mLayout == SINGLE_LINE_BOX ) &&
486 ( layout.length > layoutParameters.boundingBox.width ) ) );
490 isAutoScrollEnabled = false;
491 // Do not layout more lines if ellipsis is enabled.
493 // The last line needs to be completely filled with characters.
494 // Part of a word may be used.
496 LineRun* lineRun = nullptr;
497 LineLayout ellipsisLayout;
498 if( 0u != numberOfLines )
500 // Get the last line and layout it again with the 'completelyFill' flag to true.
501 lineRun = linesBuffer + ( numberOfLines - 1u );
503 penY -= layout.ascender - lineRun->descender + lineRun->lineSpacing;
505 ellipsisLayout.glyphIndex = lineRun->glyphRun.glyphIndex;
509 // At least there is space reserved for one line.
510 lineRun = linesBuffer;
512 lineRun->glyphRun.glyphIndex = 0u;
513 ellipsisLayout.glyphIndex = 0u;
518 GetLineLayoutForBox( layoutParameters,
520 currentParagraphDirection,
523 lineRun->glyphRun.numberOfGlyphs = ellipsisLayout.numberOfGlyphs;
524 lineRun->characterRun.characterIndex = ellipsisLayout.characterIndex;
525 lineRun->characterRun.numberOfCharacters = ellipsisLayout.numberOfCharacters;
526 lineRun->width = ellipsisLayout.length;
527 lineRun->extraLength = std::ceil( ellipsisLayout.wsLengthEndOfLine );
528 lineRun->ascender = ellipsisLayout.ascender;
529 lineRun->descender = ellipsisLayout.descender;
530 lineRun->direction = LTR;
531 lineRun->ellipsis = true;
533 layoutSize.width = layoutParameters.boundingBox.width;
534 if( layoutSize.height < Math::MACHINE_EPSILON_1000 )
536 layoutSize.height += ( lineRun->ascender + -lineRun->descender ) + lineRun->lineSpacing;
539 const GlyphInfo* const glyphsBuffer = layoutParameters.textModel->mVisualModel->mGlyphs.Begin();
540 const float outlineWidth = static_cast<float>( layoutParameters.textModel->GetOutlineWidth() );
542 SetGlyphPositions( glyphsBuffer + lineRun->glyphRun.glyphIndex,
543 ellipsisLayout.numberOfGlyphs,
545 layoutParameters.interGlyphExtraAdvance,
546 glyphPositionsBuffer + lineRun->glyphRun.glyphIndex - layoutParameters.startGlyphIndex );
553 * @brief Updates the text layout with a new laid-out line.
555 * @param[in] layoutParameters The parameters needed to layout the text.
556 * @param[in] layout The line layout.
557 * @param[in,out] layoutSize The text's layout size.
558 * @param[in,out] linesBuffer Pointer to the line's buffer.
559 * @param[in] index Index to the vector of glyphs.
560 * @param[in,out] numberOfLines The number of laid-out lines.
561 * @param[in] isLastLine Whether the laid-out line is the last one.
563 void UpdateTextLayout( const Parameters& layoutParameters,
564 const LineLayout& layout,
566 LineRun* linesBuffer,
568 Length& numberOfLines,
571 LineRun& lineRun = *( linesBuffer + numberOfLines );
574 lineRun.glyphRun.glyphIndex = index;
575 lineRun.glyphRun.numberOfGlyphs = layout.numberOfGlyphs;
576 lineRun.characterRun.characterIndex = layout.characterIndex;
577 lineRun.characterRun.numberOfCharacters = layout.numberOfCharacters;
578 lineRun.lineSpacing = mDefaultLineSpacing;
580 if( isLastLine && !layoutParameters.isLastNewParagraph )
582 const float width = layout.length + layout.wsLengthEndOfLine;
583 if( MULTI_LINE_BOX == mLayout )
585 lineRun.width = ( width > layoutParameters.boundingBox.width ) ? layoutParameters.boundingBox.width : width;
589 lineRun.width = width;
592 lineRun.extraLength = 0.f;
596 lineRun.width = layout.length;
597 lineRun.extraLength = std::ceil( layout.wsLengthEndOfLine );
600 // Rounds upward to avoid a non integer size.
601 lineRun.width = std::ceil( lineRun.width );
603 lineRun.ascender = layout.ascender;
604 lineRun.descender = layout.descender;
605 lineRun.direction = LTR;
606 lineRun.ellipsis = false;
608 // Update the actual size.
609 if( lineRun.width > layoutSize.width )
611 layoutSize.width = lineRun.width;
614 layoutSize.height += ( lineRun.ascender + -lineRun.descender ) + lineRun.lineSpacing;
618 * @brief Updates the text layout with the last laid-out line.
620 * @param[in] layoutParameters The parameters needed to layout the text.
621 * @param[in] characterIndex The character index of the line.
622 * @param[in] glyphIndex The glyph index of the line.
623 * @param[in,out] layoutSize The text's layout size.
624 * @param[in,out] linesBuffer Pointer to the line's buffer.
625 * @param[in,out] numberOfLines The number of laid-out lines.
627 void UpdateTextLayout( const Parameters& layoutParameters,
628 CharacterIndex characterIndex,
629 GlyphIndex glyphIndex,
631 LineRun* linesBuffer,
632 Length& numberOfLines )
634 const Vector<GlyphInfo>& glyphs = layoutParameters.textModel->mVisualModel->mGlyphs;
636 // Need to add a new line with no characters but with height to increase the layoutSize.height
637 const GlyphInfo& glyphInfo = glyphs[glyphs.Count() - 1u];
639 Text::FontMetrics fontMetrics;
640 if( 0u != glyphInfo.fontId )
642 mMetrics->GetFontMetrics( glyphInfo.fontId, fontMetrics );
645 LineRun& lineRun = *( linesBuffer + numberOfLines );
648 lineRun.glyphRun.glyphIndex = glyphIndex;
649 lineRun.glyphRun.numberOfGlyphs = 0u;
650 lineRun.characterRun.characterIndex = characterIndex;
651 lineRun.characterRun.numberOfCharacters = 0u;
653 lineRun.ascender = fontMetrics.ascender;
654 lineRun.descender = fontMetrics.descender;
655 lineRun.extraLength = 0.f;
656 lineRun.alignmentOffset = 0.f;
657 lineRun.direction = LTR;
658 lineRun.ellipsis = false;
659 lineRun.lineSpacing = mDefaultLineSpacing;
661 layoutSize.height += ( lineRun.ascender + -lineRun.descender ) + lineRun.lineSpacing;
665 * @brief Updates the text's layout size adding the size of the previously laid-out lines.
667 * @param[in] lines The vector of lines (before the new laid-out lines are inserted).
668 * @param[in,out] layoutSize The text's layout size.
670 void UpdateLayoutSize( const Vector<LineRun>& lines,
673 for( Vector<LineRun>::ConstIterator it = lines.Begin(),
678 const LineRun& line = *it;
680 if( line.width > layoutSize.width )
682 layoutSize.width = line.width;
685 layoutSize.height += ( line.ascender + -line.descender ) + line.lineSpacing;
690 * @brief Updates the indices of the character and glyph runs of the lines before the new lines are inserted.
692 * @param[in] layoutParameters The parameters needed to layout the text.
693 * @param[in,out] lines The vector of lines (before the new laid-out lines are inserted).
694 * @param[in] characterOffset The offset to be added to the runs of characters.
695 * @param[in] glyphOffset The offset to be added to the runs of glyphs.
697 void UpdateLineIndexOffsets( const Parameters& layoutParameters,
698 Vector<LineRun>& lines,
699 Length characterOffset,
702 // Update the glyph and character runs.
703 for( Vector<LineRun>::Iterator it = lines.Begin() + layoutParameters.startLineIndex,
710 line.glyphRun.glyphIndex = glyphOffset;
711 line.characterRun.characterIndex = characterOffset;
713 glyphOffset += line.glyphRun.numberOfGlyphs;
714 characterOffset += line.characterRun.numberOfCharacters;
718 bool LayoutText( const Parameters& layoutParameters,
719 Vector<Vector2>& glyphPositions,
720 Vector<LineRun>& lines,
722 bool elideTextEnabled,
723 bool& isAutoScrollEnabled )
725 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "-->LayoutText\n" );
726 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " box size %f, %f\n", layoutParameters.boundingBox.width, layoutParameters.boundingBox.height );
728 if( 0u == layoutParameters.numberOfGlyphs )
730 // Add an extra line if the last character is a new paragraph character and the last line doesn't have zero characters.
731 if( layoutParameters.isLastNewParagraph )
733 Length numberOfLines = lines.Count();
734 if( 0u != numberOfLines )
736 const LineRun& lastLine = *( lines.End() - 1u );
738 if( 0u != lastLine.characterRun.numberOfCharacters )
740 // Need to add a new line with no characters but with height to increase the layoutSize.height
742 Initialize( newLine );
743 lines.PushBack( newLine );
745 UpdateTextLayout( layoutParameters,
746 lastLine.characterRun.characterIndex + lastLine.characterRun.numberOfCharacters,
747 lastLine.glyphRun.glyphIndex + lastLine.glyphRun.numberOfGlyphs,
755 // Calculates the layout size.
756 UpdateLayoutSize( lines,
759 // Rounds upward to avoid a non integer size.
760 layoutSize.height = std::ceil( layoutSize.height );
762 // Nothing else do if there are no glyphs to layout.
766 const GlyphIndex lastGlyphPlusOne = layoutParameters.startGlyphIndex + layoutParameters.numberOfGlyphs;
767 const Length totalNumberOfGlyphs = layoutParameters.textModel->mVisualModel->mGlyphs.Count();
769 // In a previous layout, an extra line with no characters may have been added if the text ended with a new paragraph character.
770 // This extra line needs to be removed.
771 if( 0u != lines.Count() )
773 Vector<LineRun>::Iterator lastLine = lines.End() - 1u;
775 if( ( 0u == lastLine->characterRun.numberOfCharacters ) &&
776 ( lastGlyphPlusOne == totalNumberOfGlyphs ) )
778 lines.Remove( lastLine );
782 // Retrieve BiDi info.
783 const bool hasBidiParagraphs = !layoutParameters.textModel->mLogicalModel->mBidirectionalParagraphInfo.Empty();
785 const CharacterDirection* const characterDirectionBuffer = hasBidiParagraphs ? layoutParameters.textModel->mLogicalModel->mCharacterDirections.Begin() : nullptr;
787 // Set the first paragraph's direction.
788 CharacterDirection paragraphDirection = ( nullptr != characterDirectionBuffer ) ? *characterDirectionBuffer : LTR;
790 // Whether the layout is being updated or set from scratch.
791 const bool updateCurrentBuffer = layoutParameters.numberOfGlyphs < totalNumberOfGlyphs;
793 Vector2* glyphPositionsBuffer = nullptr;
794 Vector<Vector2> newGlyphPositions;
796 LineRun* linesBuffer = nullptr;
797 Vector<LineRun> newLines;
799 // Estimate the number of lines.
800 Length linesCapacity = std::max( 1u, layoutParameters.estimatedNumberOfLines );
801 Length numberOfLines = 0u;
803 if( updateCurrentBuffer )
805 newGlyphPositions.Resize( layoutParameters.numberOfGlyphs );
806 glyphPositionsBuffer = newGlyphPositions.Begin();
808 newLines.Resize( linesCapacity );
809 linesBuffer = newLines.Begin();
813 glyphPositionsBuffer = glyphPositions.Begin();
815 lines.Resize( linesCapacity );
816 linesBuffer = lines.Begin();
820 const GlyphInfo* const glyphsBuffer = layoutParameters.textModel->mVisualModel->mGlyphs.Begin();
821 const float outlineWidth = static_cast<float>( layoutParameters.textModel->GetOutlineWidth() );
823 float penY = CalculateLineOffset( lines,
824 layoutParameters.startLineIndex );
827 for( GlyphIndex index = layoutParameters.startGlyphIndex; index < lastGlyphPlusOne; )
829 CharacterDirection currentParagraphDirection = paragraphDirection;
831 // Get the layout for the line.
833 layout.glyphIndex = index;
834 GetLineLayoutForBox( layoutParameters,
839 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " glyph index %d\n", layout.glyphIndex );
840 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " character index %d\n", layout.characterIndex );
841 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " number of glyphs %d\n", layout.numberOfGlyphs );
842 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " number of characters %d\n", layout.numberOfCharacters );
843 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " length %f\n", layout.length );
845 if( 0u == layout.numberOfGlyphs )
847 // The width is too small and no characters are laid-out.
848 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--LayoutText width too small!\n\n" );
850 lines.Resize( numberOfLines );
852 // Rounds upward to avoid a non integer size.
853 layoutSize.height = std::ceil( layoutSize.height );
858 // Set the line position. Discard if ellipsis is enabled and the position exceeds the boundaries
860 penY += layout.ascender;
862 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " pen y %f\n", penY );
864 bool ellipsis = false;
865 if( elideTextEnabled )
867 // Does the ellipsis of the last line.
868 ellipsis = EllipsisLine( layoutParameters,
872 glyphPositionsBuffer,
875 currentParagraphDirection,
876 isAutoScrollEnabled );
881 // No more lines to layout.
886 // Whether the last line has been laid-out.
887 const bool isLastLine = index + layout.numberOfGlyphs == totalNumberOfGlyphs;
889 if( numberOfLines == linesCapacity )
891 // Reserve more space for the next lines.
892 linesBuffer = ResizeLinesBuffer( lines,
895 updateCurrentBuffer );
898 // Updates the current text's layout with the line's layout.
899 UpdateTextLayout( layoutParameters,
907 const GlyphIndex nextIndex = index + layout.numberOfGlyphs;
909 if( ( nextIndex == totalNumberOfGlyphs ) &&
910 layoutParameters.isLastNewParagraph &&
911 ( mLayout == MULTI_LINE_BOX ) )
913 // The last character of the text is a new paragraph character.
914 // An extra line with no characters is added to increase the text's height
915 // in order to place the cursor.
917 if( numberOfLines == linesCapacity )
919 // Reserve more space for the next lines.
920 linesBuffer = ResizeLinesBuffer( lines,
923 updateCurrentBuffer );
926 UpdateTextLayout( layoutParameters,
927 layout.characterIndex + layout.numberOfCharacters,
928 index + layout.numberOfGlyphs,
932 } // whether to add a last line.
934 // Sets the positions of the glyphs.
935 SetGlyphPositions( glyphsBuffer + index,
936 layout.numberOfGlyphs,
938 layoutParameters.interGlyphExtraAdvance,
939 glyphPositionsBuffer + index - layoutParameters.startGlyphIndex );
941 // Updates the vertical pen's position.
942 penY += -layout.descender + layout.lineSpacing + mDefaultLineSpacing;
944 // Increase the glyph index.
947 } // end for() traversing glyphs.
949 if( updateCurrentBuffer )
951 glyphPositions.Insert( glyphPositions.Begin() + layoutParameters.startGlyphIndex,
952 newGlyphPositions.Begin(),
953 newGlyphPositions.End() );
954 glyphPositions.Resize( totalNumberOfGlyphs );
956 newLines.Resize( numberOfLines );
958 // Current text's layout size adds only the newly laid-out lines.
959 // Updates the layout size with the previously laid-out lines.
960 UpdateLayoutSize( lines,
963 if( 0u != newLines.Count() )
965 const LineRun& lastLine = *( newLines.End() - 1u );
967 const Length characterOffset = lastLine.characterRun.characterIndex + lastLine.characterRun.numberOfCharacters;
968 const Length glyphOffset = lastLine.glyphRun.glyphIndex + lastLine.glyphRun.numberOfGlyphs;
970 // Update the indices of the runs before the new laid-out lines are inserted.
971 UpdateLineIndexOffsets( layoutParameters,
977 lines.Insert( lines.Begin() + layoutParameters.startLineIndex,
984 lines.Resize( numberOfLines );
987 // Rounds upward to avoid a non integer size.
988 layoutSize.height = std::ceil( layoutSize.height );
990 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--LayoutText\n\n" );
995 void ReLayoutRightToLeftLines( const Parameters& layoutParameters,
996 CharacterIndex startIndex,
997 Length numberOfCharacters,
998 Vector<Vector2>& glyphPositions )
1000 const GlyphInfo* const glyphsBuffer = layoutParameters.textModel->mVisualModel->mGlyphs.Begin();
1001 const GlyphIndex* const charactersToGlyphsBuffer = layoutParameters.textModel->mVisualModel->mCharactersToGlyph.Begin();
1002 const Length* const glyphsPerCharacterBuffer = layoutParameters.textModel->mVisualModel->mGlyphsPerCharacter.Begin();
1003 const float outlineWidth = static_cast<float>( layoutParameters.textModel->GetOutlineWidth() );
1005 const CharacterIndex lastCharacterIndex = startIndex + numberOfCharacters;
1007 // Traverses the paragraphs with right to left characters.
1008 for( LineIndex lineIndex = 0u; lineIndex < layoutParameters.numberOfBidirectionalInfoRuns; ++lineIndex )
1010 const BidirectionalLineInfoRun& bidiLine = *( layoutParameters.lineBidirectionalInfoRunsBuffer + lineIndex );
1012 if( startIndex >= bidiLine.characterRun.characterIndex + bidiLine.characterRun.numberOfCharacters )
1014 // Do not reorder the line if it has been already reordered.
1018 if( bidiLine.characterRun.characterIndex >= lastCharacterIndex )
1020 // Do not reorder the lines after the last requested character.
1024 const CharacterIndex characterVisualIndex = bidiLine.characterRun.characterIndex + *bidiLine.visualToLogicalMap;
1025 const GlyphInfo& glyph = *( glyphsBuffer + *( charactersToGlyphsBuffer + characterVisualIndex ) );
1027 float penX = -glyph.xBearing + outlineWidth + mCursorWidth;
1029 Vector2* glyphPositionsBuffer = glyphPositions.Begin();
1031 // Traverses the characters of the right to left paragraph.
1032 for( CharacterIndex characterLogicalIndex = 0u;
1033 characterLogicalIndex < bidiLine.characterRun.numberOfCharacters;
1034 ++characterLogicalIndex )
1036 // Convert the character in the logical order into the character in the visual order.
1037 const CharacterIndex characterVisualIndex = bidiLine.characterRun.characterIndex + *( bidiLine.visualToLogicalMap + characterLogicalIndex );
1039 // Get the number of glyphs of the character.
1040 const Length numberOfGlyphs = *( glyphsPerCharacterBuffer + characterVisualIndex );
1042 for( GlyphIndex index = 0u; index < numberOfGlyphs; ++index )
1044 // Convert the character in the visual order into the glyph in the visual order.
1045 const GlyphIndex glyphIndex = *( charactersToGlyphsBuffer + characterVisualIndex ) + index;
1047 DALI_ASSERT_DEBUG( 0u <= glyphIndex && glyphIndex < layoutParameters.textModel->mVisualModel->mGlyphs.Count() );
1049 const GlyphInfo& glyph = *( glyphsBuffer + glyphIndex );
1050 Vector2& position = *( glyphPositionsBuffer + glyphIndex );
1052 position.x = std::round( penX + glyph.xBearing );
1053 penX += ( glyph.advance + layoutParameters.interGlyphExtraAdvance );
1059 void Align( const Size& size,
1060 CharacterIndex startIndex,
1061 Length numberOfCharacters,
1062 Text::HorizontalAlignment::Type horizontalAlignment,
1063 Vector<LineRun>& lines,
1064 float& alignmentOffset,
1065 Dali::LayoutDirection::Type layoutDirection,
1066 bool matchSystemLanguageDirection )
1068 const CharacterIndex lastCharacterPlusOne = startIndex + numberOfCharacters;
1070 alignmentOffset = MAX_FLOAT;
1071 // Traverse all lines and align the glyphs.
1072 for( Vector<LineRun>::Iterator it = lines.Begin(), endIt = lines.End();
1076 LineRun& line = *it;
1078 if( line.characterRun.characterIndex < startIndex )
1080 // Do not align lines which have already been aligned.
1084 if( line.characterRun.characterIndex > lastCharacterPlusOne )
1086 // Do not align lines beyond the last laid-out character.
1090 if( line.characterRun.characterIndex == lastCharacterPlusOne && !isEmptyLineAtLast( lines, it ) )
1092 // Do not align lines beyond the last laid-out character unless the line is last and empty.
1096 // Calculate the line's alignment offset accordingly with the align option,
1097 // the box width, line length, and the paragraph's direction.
1098 CalculateHorizontalAlignment( size.width,
1099 horizontalAlignment,
1102 matchSystemLanguageDirection );
1104 // Updates the alignment offset.
1105 alignmentOffset = std::min( alignmentOffset, line.alignmentOffset );
1109 void CalculateHorizontalAlignment( float boxWidth,
1110 HorizontalAlignment::Type horizontalAlignment,
1112 Dali::LayoutDirection::Type layoutDirection,
1113 bool matchSystemLanguageDirection )
1115 line.alignmentOffset = 0.f;
1116 const bool isLineRTL = RTL == line.direction;
1117 // Whether to swap the alignment.
1118 // 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.
1119 bool isLayoutRTL = isLineRTL;
1120 float lineLength = line.width;
1122 // match align for system language direction
1123 if( matchSystemLanguageDirection )
1125 // Swap the alignment type if the line is right to left.
1126 isLayoutRTL = layoutDirection == LayoutDirection::RIGHT_TO_LEFT;
1128 // Calculate the horizontal line offset.
1129 switch( horizontalAlignment )
1131 case HorizontalAlignment::BEGIN:
1137 lineLength += line.extraLength;
1140 line.alignmentOffset = boxWidth - lineLength;
1144 line.alignmentOffset = 0.f;
1148 // 'Remove' the white spaces at the end of the line (which are at the beginning in visual order)
1149 line.alignmentOffset -= line.extraLength;
1154 case HorizontalAlignment::CENTER:
1156 line.alignmentOffset = 0.5f * ( boxWidth - lineLength );
1160 line.alignmentOffset -= line.extraLength;
1163 line.alignmentOffset = std::floor( line.alignmentOffset ); // floor() avoids pixel alignment issues.
1166 case HorizontalAlignment::END:
1170 line.alignmentOffset = 0.f;
1174 // 'Remove' the white spaces at the end of the line (which are at the beginning in visual order)
1175 line.alignmentOffset -= line.extraLength;
1182 lineLength += line.extraLength;
1185 line.alignmentOffset = boxWidth - lineLength;
1192 void Initialize( LineRun& line )
1194 line.glyphRun.glyphIndex = 0u;
1195 line.glyphRun.numberOfGlyphs = 0u;
1196 line.characterRun.characterIndex = 0u;
1197 line.characterRun.numberOfCharacters = 0u;
1199 line.ascender = 0.f;
1200 line.descender = 0.f;
1201 line.extraLength = 0.f;
1202 line.alignmentOffset = 0.f;
1203 line.direction = LTR;
1204 line.ellipsis = false;
1205 line.lineSpacing = mDefaultLineSpacing;
1210 float mDefaultLineSpacing;
1212 IntrusivePtr<Metrics> mMetrics;
1218 mImpl = new Engine::Impl();
1226 void Engine::SetMetrics( MetricsPtr& metrics )
1228 mImpl->mMetrics = metrics;
1231 void Engine::SetLayout( Type layout )
1233 mImpl->mLayout = layout;
1236 Engine::Type Engine::GetLayout() const
1238 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "GetLayout[%d]\n", mImpl->mLayout);
1239 return mImpl->mLayout;
1242 void Engine::SetCursorWidth( int width )
1244 mImpl->mCursorWidth = static_cast<float>( width );
1247 int Engine::GetCursorWidth() const
1249 return static_cast<int>( mImpl->mCursorWidth );
1252 bool Engine::LayoutText( const Parameters& layoutParameters,
1253 Vector<Vector2>& glyphPositions,
1254 Vector<LineRun>& lines,
1256 bool elideTextEnabled,
1257 bool& isAutoScrollEnabled )
1259 return mImpl->LayoutText( layoutParameters,
1264 isAutoScrollEnabled );
1267 void Engine::ReLayoutRightToLeftLines( const Parameters& layoutParameters,
1268 CharacterIndex startIndex,
1269 Length numberOfCharacters,
1270 Vector<Vector2>& glyphPositions )
1272 mImpl->ReLayoutRightToLeftLines( layoutParameters,
1278 void Engine::Align( const Size& size,
1279 CharacterIndex startIndex,
1280 Length numberOfCharacters,
1281 Text::HorizontalAlignment::Type horizontalAlignment,
1282 Vector<LineRun>& lines,
1283 float& alignmentOffset,
1284 Dali::LayoutDirection::Type layoutDirection,
1285 bool matchSystemLanguageDirection )
1290 horizontalAlignment,
1294 matchSystemLanguageDirection );
1297 void Engine::SetDefaultLineSpacing( float lineSpacing )
1299 mImpl->mDefaultLineSpacing = lineSpacing;
1302 float Engine::GetDefaultLineSpacing() const
1304 return mImpl->mDefaultLineSpacing;
1307 } // namespace Layout
1311 } // namespace Toolkit