2 * Copyright (c) 2015 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>
23 #include <dali/integration-api/debug.h>
24 #include <dali/devel-api/text-abstraction/font-client.h>
27 #include <dali-toolkit/internal/text/bidirectional-line-info-run.h>
28 #include <dali-toolkit/internal/text/cursor-helper-functions.h>
29 #include <dali-toolkit/internal/text/glyph-metrics-helper.h>
30 #include <dali-toolkit/internal/text/layouts/layout-parameters.h>
44 #if defined(DEBUG_ENABLED)
45 Debug::Filter* gLogFilter = Debug::Filter::New(Debug::Concise, true, "LOG_TEXT_LAYOUT");
48 const float MAX_FLOAT = std::numeric_limits<float>::max();
49 const bool RTL = true;
50 const float CURSOR_WIDTH = 1.f;
55 * @brief Stores temporary layout info of the line.
63 numberOfCharacters( 0u ),
67 wsLengthEndOfLine( 0.f ),
69 descender( MAX_FLOAT )
80 numberOfCharacters = 0u;
84 wsLengthEndOfLine = 0.f;
86 descender = MAX_FLOAT;
89 GlyphIndex glyphIndex; ///< Index of the first glyph to be laid-out.
90 CharacterIndex characterIndex; ///< Index of the first character to be laid-out.
91 Length numberOfGlyphs; ///< The number of glyph which fit in one line.
92 Length numberOfCharacters; ///< The number of characters which fit in one line.
93 float length; ///< The addition of the advance metric of all the glyphs which fit in one line.
94 float extraBearing; ///< The extra width to be added to the line's length when the bearing of the first glyph is negative.
95 float extraWidth; ///< The extra width to be added to the line's length when the bearing + width of the last glyph is greater than the advance.
96 float wsLengthEndOfLine; ///< The length of the white spaces at the end of the line.
97 float ascender; ///< The maximum ascender of all fonts in the line.
98 float descender; ///< The minimum descender of all fonts in the line.
101 struct LayoutEngine::Impl
104 : mLayout( LayoutEngine::SINGLE_LINE_BOX ),
105 mHorizontalAlignment( LayoutEngine::HORIZONTAL_ALIGN_BEGIN ),
106 mVerticalAlignment( LayoutEngine::VERTICAL_ALIGN_TOP ),
107 mCursorWidth( CURSOR_WIDTH ),
108 mEllipsisEnabled( false )
113 * @brief Updates the line ascender and descender with the metrics of a new font.
115 * @param[in] fontId The id of the new font.
116 * @param[in,out] lineLayout The line layout.
118 void UpdateLineHeight( FontId fontId, LineLayout& lineLayout )
120 Text::FontMetrics fontMetrics;
121 mMetrics->GetFontMetrics( fontId, fontMetrics );
123 // Sets the maximum ascender.
124 if( fontMetrics.ascender > lineLayout.ascender )
126 lineLayout.ascender = fontMetrics.ascender;
129 // Sets the minimum descender.
130 if( fontMetrics.descender < lineLayout.descender )
132 lineLayout.descender = fontMetrics.descender;
137 * @brief Merges a temporary line layout into the line layout.
139 * @param[in,out] lineLayout The line layout.
140 * @param[in] tmpLineLayout A temporary line layout.
142 void MergeLineLayout( LineLayout& lineLayout,
143 const LineLayout& tmpLineLayout )
145 lineLayout.numberOfCharacters += tmpLineLayout.numberOfCharacters;
146 lineLayout.numberOfGlyphs += tmpLineLayout.numberOfGlyphs;
147 lineLayout.length += tmpLineLayout.length;
149 if( 0.f < tmpLineLayout.length )
151 lineLayout.length += lineLayout.wsLengthEndOfLine;
153 lineLayout.wsLengthEndOfLine = tmpLineLayout.wsLengthEndOfLine;
157 lineLayout.wsLengthEndOfLine += tmpLineLayout.wsLengthEndOfLine;
160 if( tmpLineLayout.ascender > lineLayout.ascender )
162 lineLayout.ascender = tmpLineLayout.ascender;
165 if( tmpLineLayout.descender < lineLayout.descender )
167 lineLayout.descender = tmpLineLayout.descender;
172 * Retrieves the line layout for a given box width.
174 * @note This method lais out text as it were left to right. At this point is not possible to reorder the line
175 * because the number of characters of the line is not known (one of the responsabilities of this method
176 * is calculate that). Due to glyph's 'x' bearing, width and advance, when right to left or mixed right to left
177 * and left to right text is laid-out, it can be small differences in the line length. One solution is to
178 * reorder and re-lay out the text after this method and add or remove one extra glyph if needed. However,
179 * this method calculates which are the first and last glyphs of the line (the ones that causes the
180 * differences). This is a good point to check if there is problems with the text exceeding the boundaries
181 * of the control when there is right to left text.
183 * @param[in] parameters The layout parameters.
184 * @param[out] lineLayout The line layout.
185 * @param[in,out] paragraphDirection in: the current paragraph's direction, out: the next paragraph's direction. Is set after a must break.
186 * @param[in] completelyFill Whether to completely fill the line ( even if the last word exceeds the boundaries ).
188 void GetLineLayoutForBox( const LayoutParameters& parameters,
189 LineLayout& lineLayout,
190 CharacterDirection& paragraphDirection,
191 bool completelyFill )
193 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "-->GetLineLayoutForBox\n" );
194 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " initial glyph index : %d\n", lineLayout.glyphIndex );
195 // Stores temporary line layout which has not been added to the final line layout.
196 LineLayout tmpLineLayout;
198 const bool isMultiline = mLayout == MULTI_LINE_BOX;
200 // The last glyph to be laid-out.
201 const GlyphIndex lastGlyphOfParagraphPlusOne = parameters.startGlyphIndex + parameters.numberOfGlyphs;
203 // If the first glyph has a negative bearing its absolute value needs to be added to the line length.
204 // In the case the line starts with a right to left character, if the width is longer than the advance,
205 // the difference needs to be added to the line length.
207 // Check whether the first glyph comes from a character that is shaped in multiple glyphs.
208 const Length numberOfGLyphsInGroup = GetNumberOfGlyphsOfGroup( lineLayout.glyphIndex,
209 lastGlyphOfParagraphPlusOne,
210 parameters.charactersPerGlyphBuffer );
212 GlyphMetrics glyphMetrics;
213 GetGlyphsMetrics( lineLayout.glyphIndex,
214 numberOfGLyphsInGroup,
216 parameters.glyphsBuffer,
219 // Set the direction of the first character of the line.
220 lineLayout.characterIndex = *( parameters.glyphsToCharactersBuffer + lineLayout.glyphIndex );
221 const CharacterDirection firstCharacterDirection = ( NULL == parameters.characterDirectionBuffer ) ? false : *( parameters.characterDirectionBuffer + lineLayout.characterIndex );
222 CharacterDirection previousCharacterDirection = firstCharacterDirection;
224 const float extraWidth = glyphMetrics.xBearing + glyphMetrics.width - glyphMetrics.advance;
225 float tmpExtraWidth = ( 0.f < extraWidth ) ? extraWidth : 0.f;
227 float tmpExtraBearing = ( 0.f > glyphMetrics.xBearing ) ? -glyphMetrics.xBearing : 0.f;
229 tmpLineLayout.length += mCursorWidth; // Added to give some space to the cursor.
231 // Calculate the line height if there is no characters.
232 FontId lastFontId = glyphMetrics.fontId;
233 UpdateLineHeight( lastFontId, tmpLineLayout );
235 bool oneWordLaidOut = false;
237 for( GlyphIndex glyphIndex = lineLayout.glyphIndex;
238 glyphIndex < lastGlyphOfParagraphPlusOne; )
240 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " glyph index : %d\n", glyphIndex );
242 // Check whether this glyph comes from a character that is shaped in multiple glyphs.
243 const Length numberOfGLyphsInGroup = GetNumberOfGlyphsOfGroup( glyphIndex,
244 lastGlyphOfParagraphPlusOne,
245 parameters.charactersPerGlyphBuffer );
247 GlyphMetrics glyphMetrics;
248 GetGlyphsMetrics( glyphIndex,
249 numberOfGLyphsInGroup,
251 parameters.glyphsBuffer,
254 const bool isLastGlyph = glyphIndex + numberOfGLyphsInGroup == parameters.totalNumberOfGlyphs;
256 // Check if the font of the current glyph is the same of the previous one.
257 // If it's different the ascender and descender need to be updated.
258 if( lastFontId != glyphMetrics.fontId )
260 UpdateLineHeight( glyphMetrics.fontId, tmpLineLayout );
261 lastFontId = glyphMetrics.fontId;
264 // Get the character indices for the current glyph. The last character index is needed
265 // because there are glyphs formed by more than one character but their break info is
266 // given only for the last character.
267 const Length charactersPerGlyph = *( parameters.charactersPerGlyphBuffer + glyphIndex + numberOfGLyphsInGroup - 1u );
268 const bool hasCharacters = charactersPerGlyph > 0u;
269 const CharacterIndex characterFirstIndex = *( parameters.glyphsToCharactersBuffer + glyphIndex );
270 const CharacterIndex characterLastIndex = characterFirstIndex + ( hasCharacters ? charactersPerGlyph - 1u : 0u );
272 // Get the line break info for the current character.
273 const LineBreakInfo lineBreakInfo = hasCharacters ? *( parameters.lineBreakInfoBuffer + characterLastIndex ) : TextAbstraction::LINE_NO_BREAK;
275 // Get the word break info for the current character.
276 const WordBreakInfo wordBreakInfo = *( parameters.wordBreakInfoBuffer + characterLastIndex );
278 // Increase the number of characters.
279 tmpLineLayout.numberOfCharacters += charactersPerGlyph;
281 // Increase the number of glyphs.
282 tmpLineLayout.numberOfGlyphs += numberOfGLyphsInGroup;
284 // Check whether is a white space.
285 const Character character = *( parameters.textBuffer + characterFirstIndex );
286 const bool isWhiteSpace = TextAbstraction::IsWhiteSpace( character );
288 // Used to restore the temporal line layout when a single word does not fit in the control's width and is split by character.
289 const float previousTmpLineLength = tmpLineLayout.length;
290 const float previousTmpExtraBearing = tmpExtraBearing;
291 const float previousTmpExtraWidth = tmpExtraWidth;
293 // Get the character's direction.
294 const CharacterDirection characterDirection = ( NULL == parameters.characterDirectionBuffer ) ? false : *( parameters.characterDirectionBuffer + characterFirstIndex );
296 // Increase the accumulated length.
299 // Add the length to the length of white spaces at the end of the line.
300 tmpLineLayout.wsLengthEndOfLine += glyphMetrics.advance; // The advance is used as the width is always zero for the white spaces.
304 // Add as well any previous white space length.
305 tmpLineLayout.length += tmpLineLayout.wsLengthEndOfLine + glyphMetrics.advance;
307 // An extra space may be added to the line for the first and last glyph of the line.
308 // If the bearing of the first glyph is negative, its positive value needs to be added.
309 // If the bearing plus the width of the last glyph is greater than the advance, the difference
310 // needs to be added.
312 if( characterDirection == paragraphDirection )
314 if( RTL == characterDirection )
325 tmpExtraBearing = ( 0.f > glyphMetrics.xBearing ) ? -glyphMetrics.xBearing : 0.f;
338 const float extraWidth = glyphMetrics.xBearing + glyphMetrics.width - glyphMetrics.advance;
339 tmpExtraWidth = ( 0.f < extraWidth ) ? extraWidth : 0.f;
344 if( characterDirection != previousCharacterDirection )
346 if( RTL == characterDirection )
351 const float extraWidth = glyphMetrics.xBearing + glyphMetrics.width - glyphMetrics.advance;
352 tmpExtraWidth = ( 0.f < extraWidth ) ? extraWidth : 0.f;
359 tmpExtraBearing = ( 0.f > glyphMetrics.xBearing ) ? -glyphMetrics.xBearing : 0.f;
362 else if( characterDirection == firstCharacterDirection )
364 if( RTL == characterDirection )
370 tmpExtraBearing = ( 0.f > glyphMetrics.xBearing ) ? -glyphMetrics.xBearing : 0.f;
378 const float extraWidth = glyphMetrics.xBearing + glyphMetrics.width - glyphMetrics.advance;
379 tmpExtraWidth = ( 0.f < extraWidth ) ? extraWidth : 0.f;
384 // Clear the white space length at the end of the line.
385 tmpLineLayout.wsLengthEndOfLine = 0.f;
388 // Check if the accumulated length fits in the width of the box.
389 if( ( completelyFill || isMultiline ) && !isWhiteSpace &&
390 ( tmpExtraBearing + lineLayout.length + lineLayout.wsLengthEndOfLine + tmpLineLayout.length + tmpExtraWidth > parameters.boundingBox.width ) )
392 // Current word does not fit in the box's width.
393 if( !oneWordLaidOut || completelyFill )
395 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " Break the word by character\n" );
397 // The word doesn't fit in the control's width. It needs to be split by character.
398 if( tmpLineLayout.numberOfGlyphs > 0u )
400 tmpLineLayout.numberOfCharacters -= charactersPerGlyph;
401 tmpLineLayout.numberOfGlyphs -= numberOfGLyphsInGroup;
402 tmpLineLayout.length = previousTmpLineLength;
403 tmpExtraBearing = previousTmpExtraBearing;
404 tmpExtraWidth = previousTmpExtraWidth;
407 // Add part of the word to the line layout.
408 MergeLineLayout( lineLayout, tmpLineLayout );
412 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " Current word does not fit.\n" );
415 lineLayout.extraBearing = tmpExtraBearing;
416 lineLayout.extraWidth = tmpExtraWidth;
418 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--GetLineLayoutForBox.\n" );
423 if( ( isMultiline || isLastGlyph ) &&
424 ( TextAbstraction::LINE_MUST_BREAK == lineBreakInfo ) )
426 // Must break the line. Update the line layout and return.
427 MergeLineLayout( lineLayout, tmpLineLayout );
429 // Set the next paragraph's direction.
431 ( NULL != parameters.characterDirectionBuffer ) )
433 paragraphDirection = *( parameters.characterDirectionBuffer + 1u + characterLastIndex );
436 lineLayout.extraBearing = tmpExtraBearing;
437 lineLayout.extraWidth = tmpExtraWidth;
439 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " Must break\n" );
440 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--GetLineLayoutForBox\n" );
446 ( TextAbstraction::WORD_BREAK == wordBreakInfo ) )
448 oneWordLaidOut = true;
449 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " One word laid-out\n" );
451 // Current glyph is the last one of the current word.
452 // Add the temporal layout to the current one.
453 MergeLineLayout( lineLayout, tmpLineLayout );
455 tmpLineLayout.Clear();
458 previousCharacterDirection = characterDirection;
459 glyphIndex += numberOfGLyphsInGroup;
462 lineLayout.extraBearing = tmpExtraBearing;
463 lineLayout.extraWidth = tmpExtraWidth;
465 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--GetLineLayoutForBox\n" );
468 void SetGlyphPositions( const GlyphInfo* const glyphsBuffer,
469 Length numberOfGlyphs,
470 Vector2* glyphPositionsBuffer )
472 // Traverse the glyphs and set the positions.
474 // Check if the x bearing of the first character is negative.
475 // If it has a negative x bearing, it will exceed the boundaries of the actor,
476 // so the penX position needs to be moved to the right.
478 const GlyphInfo& glyph = *glyphsBuffer;
479 float penX = ( 0.f > glyph.xBearing ) ? -glyph.xBearing : 0.f;
481 for( GlyphIndex i = 0u; i < numberOfGlyphs; ++i )
483 const GlyphInfo& glyph = *( glyphsBuffer + i );
484 Vector2& position = *( glyphPositionsBuffer + i );
486 position.x = penX + glyph.xBearing;
487 position.y = -glyph.yBearing;
489 penX += glyph.advance;
494 * @brief Resizes the line buffer.
496 * @param[in,out] lines The vector of lines. Used when the layout is created from scratch.
497 * @param[in,out] newLines The vector of lines used instead of @p lines when the layout is updated.
498 * @param[in,out] linesCapacity The capacity of the vector (either lines or newLines).
499 * @param[in] updateCurrentBuffer Whether the layout is updated.
501 * @return Pointer to either lines or newLines.
503 LineRun* ResizeLinesBuffer( Vector<LineRun>& lines,
504 Vector<LineRun>& newLines,
505 Length& linesCapacity,
506 bool updateCurrentBuffer )
508 LineRun* linesBuffer = NULL;
509 // Reserve more space for the next lines.
511 if( updateCurrentBuffer )
513 newLines.Resize( linesCapacity );
514 linesBuffer = newLines.Begin();
518 lines.Resize( linesCapacity );
519 linesBuffer = lines.Begin();
526 * Ellipsis a line if it exceeds the width's of the bounding box.
528 * @param[in] layoutParameters The parameters needed to layout the text.
529 * @param[in] layout The line layout.
530 * @param[in,out] layoutSize The text's layout size.
531 * @param[in,out] linesBuffer Pointer to the line's buffer.
532 * @param[in,out] glyphPositionsBuffer Pointer to the position's buffer.
533 * @param[in,out] numberOfLines The number of laid-out lines.
534 * @param[in] penY The vertical layout position.
535 * @param[in] currentParagraphDirection The current paragraph's direction.
537 * return Whether the line is ellipsized.
539 bool EllipsisLine( const LayoutParameters& layoutParameters,
540 const LineLayout& layout,
542 LineRun* linesBuffer,
543 Vector2* glyphPositionsBuffer,
544 Length& numberOfLines,
546 CharacterDirection currentParagraphDirection )
548 const bool ellipsis = ( ( penY - layout.descender > layoutParameters.boundingBox.height ) ||
549 ( ( mLayout == SINGLE_LINE_BOX ) &&
550 ( layout.extraBearing + layout.length + layout.extraWidth > layoutParameters.boundingBox.width ) ) );
554 // Do not layout more lines if ellipsis is enabled.
556 // The last line needs to be completely filled with characters.
557 // Part of a word may be used.
559 LineRun* lineRun = NULL;
560 LineLayout ellipsisLayout;
561 if( 0u != numberOfLines )
563 // Get the last line and layout it again with the 'completelyFill' flag to true.
564 lineRun = linesBuffer + ( numberOfLines - 1u );
566 penY -= layout.ascender - lineRun->descender;
568 ellipsisLayout.glyphIndex = lineRun->glyphRun.glyphIndex;
572 // At least there is space reserved for one line.
573 lineRun = linesBuffer;
575 lineRun->glyphRun.glyphIndex = 0u;
576 ellipsisLayout.glyphIndex = 0u;
581 GetLineLayoutForBox( layoutParameters,
583 currentParagraphDirection,
586 lineRun->glyphRun.numberOfGlyphs = ellipsisLayout.numberOfGlyphs;
587 lineRun->characterRun.characterIndex = ellipsisLayout.characterIndex;
588 lineRun->characterRun.numberOfCharacters = ellipsisLayout.numberOfCharacters;
589 lineRun->width = ellipsisLayout.length;
590 lineRun->extraLength = ( ellipsisLayout.wsLengthEndOfLine > 0.f ) ? ellipsisLayout.wsLengthEndOfLine - ellipsisLayout.extraWidth : 0.f;
591 lineRun->ascender = ellipsisLayout.ascender;
592 lineRun->descender = ellipsisLayout.descender;
593 lineRun->direction = !RTL;
594 lineRun->ellipsis = true;
596 layoutSize.width = layoutParameters.boundingBox.width;
597 layoutSize.height += ( lineRun->ascender + -lineRun->descender );
599 SetGlyphPositions( layoutParameters.glyphsBuffer + lineRun->glyphRun.glyphIndex,
600 ellipsisLayout.numberOfGlyphs,
601 glyphPositionsBuffer + lineRun->glyphRun.glyphIndex - layoutParameters.startGlyphIndex );
608 * @brief Updates the text layout with a new laid-out line.
610 * @param[in] layoutParameters The parameters needed to layout the text.
611 * @param[in] layout The line layout.
612 * @param[in,out] layoutSize The text's layout size.
613 * @param[in,out] linesBuffer Pointer to the line's buffer.
614 * @param[in] index Index to the vector of glyphs.
615 * @param[in,out] numberOfLines The number of laid-out lines.
616 * @param[in] isLastLine Whether the laid-out line is the last one.
618 void UpdateTextLayout( const LayoutParameters& layoutParameters,
619 const LineLayout& layout,
621 LineRun* linesBuffer,
623 Length& numberOfLines,
626 LineRun& lineRun = *( linesBuffer + numberOfLines );
629 lineRun.glyphRun.glyphIndex = index;
630 lineRun.glyphRun.numberOfGlyphs = layout.numberOfGlyphs;
631 lineRun.characterRun.characterIndex = layout.characterIndex;
632 lineRun.characterRun.numberOfCharacters = layout.numberOfCharacters;
633 if( isLastLine && !layoutParameters.isLastNewParagraph )
635 const float width = layout.extraBearing + layout.length + layout.extraWidth + layout.wsLengthEndOfLine;
636 if( MULTI_LINE_BOX == mLayout )
638 lineRun.width = ( width > layoutParameters.boundingBox.width ) ? layoutParameters.boundingBox.width : width;
642 lineRun.width = width;
645 lineRun.extraLength = 0.f;
649 lineRun.width = layout.extraBearing + layout.length + layout.extraWidth;
650 lineRun.extraLength = ( layout.wsLengthEndOfLine > 0.f ) ? layout.wsLengthEndOfLine - layout.extraWidth : 0.f;
652 lineRun.ascender = layout.ascender;
653 lineRun.descender = layout.descender;
654 lineRun.direction = !RTL;
655 lineRun.ellipsis = false;
657 // Update the actual size.
658 if( lineRun.width > layoutSize.width )
660 layoutSize.width = lineRun.width;
663 layoutSize.height += ( lineRun.ascender + -lineRun.descender );
667 * @brief Updates the text layout with the last laid-out line.
669 * @param[in] layoutParameters The parameters needed to layout the text.
670 * @param[in] characterIndex The character index of the line.
671 * @param[in] glyphIndex The glyph index of the line.
672 * @param[in,out] layoutSize The text's layout size.
673 * @param[in,out] linesBuffer Pointer to the line's buffer.
674 * @param[in,out] numberOfLines The number of laid-out lines.
676 void UpdateTextLayout( const LayoutParameters& layoutParameters,
677 CharacterIndex characterIndex,
678 GlyphIndex glyphIndex,
680 LineRun* linesBuffer,
681 Length& numberOfLines )
683 // Need to add a new line with no characters but with height to increase the layoutSize.height
684 const GlyphInfo& glyphInfo = *( layoutParameters.glyphsBuffer + layoutParameters.totalNumberOfGlyphs - 1u );
686 Text::FontMetrics fontMetrics;
687 mMetrics->GetFontMetrics( glyphInfo.fontId, fontMetrics );
689 LineRun& lineRun = *( linesBuffer + numberOfLines );
692 lineRun.glyphRun.glyphIndex = glyphIndex;
693 lineRun.glyphRun.numberOfGlyphs = 0u;
694 lineRun.characterRun.characterIndex = characterIndex;
695 lineRun.characterRun.numberOfCharacters = 0u;
697 lineRun.ascender = fontMetrics.ascender;
698 lineRun.descender = fontMetrics.descender;
699 lineRun.extraLength = 0.f;
700 lineRun.alignmentOffset = 0.f;
701 lineRun.direction = !RTL;
702 lineRun.ellipsis = false;
704 layoutSize.height += ( lineRun.ascender + -lineRun.descender );
708 * @brief Updates the text's layout size adding the size of the previously laid-out lines.
710 * @param[in] lines The vector of lines (before the new laid-out lines are inserted).
711 * @param[in,out] layoutSize The text's layout size.
713 void UpdateLayoutSize( const Vector<LineRun>& lines,
716 for( Vector<LineRun>::ConstIterator it = lines.Begin(),
721 const LineRun& line = *it;
723 if( line.width > layoutSize.width )
725 layoutSize.width = line.width;
728 layoutSize.height += ( line.ascender + -line.descender );
733 * @brief Updates the indices of the character and glyph runs of the lines before the new lines are inserted.
735 * @param[in] layoutParameters The parameters needed to layout the text.
736 * @param[in,out] lines The vector of lines (before the new laid-out lines are inserted).
737 * @param[in] characterOffset The offset to be added to the runs of characters.
738 * @param[in] glyphOffset The offset to be added to the runs of glyphs.
740 void UpdateLineIndexOffsets( const LayoutParameters& layoutParameters,
741 Vector<LineRun>& lines,
742 Length characterOffset,
745 // Update the glyph and character runs.
746 for( Vector<LineRun>::Iterator it = lines.Begin() + layoutParameters.startLineIndex,
753 line.glyphRun.glyphIndex = glyphOffset;
754 line.characterRun.characterIndex = characterOffset;
756 glyphOffset += line.glyphRun.numberOfGlyphs;
757 characterOffset += line.characterRun.numberOfCharacters;
761 bool LayoutText( const LayoutParameters& layoutParameters,
762 Vector<Vector2>& glyphPositions,
763 Vector<LineRun>& lines,
766 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "-->LayoutText\n" );
767 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " box size %f, %f\n", layoutParameters.boundingBox.width, layoutParameters.boundingBox.height );
769 if( 0u == layoutParameters.numberOfGlyphs )
771 // Add an extra line if the last character is a new paragraph character and the last line doesn't have zero characters.
772 if( layoutParameters.isLastNewParagraph )
774 Length numberOfLines = lines.Count();
775 if( 0u != numberOfLines )
777 const LineRun& lastLine = *( lines.End() - 1u );
779 if( 0u != lastLine.characterRun.numberOfCharacters )
781 // Need to add a new line with no characters but with height to increase the layoutSize.height
783 Initialize( newLine );
784 lines.PushBack( newLine );
786 UpdateTextLayout( layoutParameters,
787 lastLine.characterRun.characterIndex + lastLine.characterRun.numberOfCharacters,
788 lastLine.glyphRun.glyphIndex + lastLine.glyphRun.numberOfGlyphs,
796 // Nothing else do if there are no glyphs to layout.
800 const GlyphIndex lastGlyphPlusOne = layoutParameters.startGlyphIndex + layoutParameters.numberOfGlyphs;
802 // In a previous layout, an extra line with no characters may have been added if the text ended with a new paragraph character.
803 // This extra line needs to be removed.
804 if( 0u != lines.Count() )
806 Vector<LineRun>::Iterator lastLine = lines.End() - 1u;
808 if( ( 0u == lastLine->characterRun.numberOfCharacters ) &&
809 ( lastGlyphPlusOne == layoutParameters.totalNumberOfGlyphs ) )
811 lines.Remove( lastLine );
815 // Set the first paragraph's direction.
816 CharacterDirection paragraphDirection = ( NULL != layoutParameters.characterDirectionBuffer ) ? *layoutParameters.characterDirectionBuffer : !RTL;
818 // Whether the layout is being updated or set from scratch.
819 const bool updateCurrentBuffer = layoutParameters.numberOfGlyphs < layoutParameters.totalNumberOfGlyphs;
821 Vector2* glyphPositionsBuffer = NULL;
822 Vector<Vector2> newGlyphPositions;
824 LineRun* linesBuffer = NULL;
825 Vector<LineRun> newLines;
827 // Estimate the number of lines.
828 Length linesCapacity = std::max( 1u, layoutParameters.estimatedNumberOfLines );
829 Length numberOfLines = 0u;
831 if( updateCurrentBuffer )
833 newGlyphPositions.Resize( layoutParameters.numberOfGlyphs );
834 glyphPositionsBuffer = newGlyphPositions.Begin();
836 newLines.Resize( linesCapacity );
837 linesBuffer = newLines.Begin();
841 glyphPositionsBuffer = glyphPositions.Begin();
843 lines.Resize( linesCapacity );
844 linesBuffer = lines.Begin();
847 float penY = CalculateLineOffset( lines,
848 layoutParameters.startLineIndex );
850 for( GlyphIndex index = layoutParameters.startGlyphIndex; index < lastGlyphPlusOne; )
852 CharacterDirection currentParagraphDirection = paragraphDirection;
854 // Get the layout for the line.
856 layout.glyphIndex = index;
857 GetLineLayoutForBox( layoutParameters,
862 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " glyph index %d\n", layout.glyphIndex );
863 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " character index %d\n", layout.characterIndex );
864 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " number of glyphs %d\n", layout.numberOfGlyphs );
865 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " number of characters %d\n", layout.numberOfCharacters );
866 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " length %f\n", layout.length );
868 if( 0u == layout.numberOfGlyphs )
870 // The width is too small and no characters are laid-out.
871 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--LayoutText width too small!\n\n" );
873 lines.Resize( numberOfLines );
877 // Set the line position. Discard if ellipsis is enabled and the position exceeds the boundaries
879 penY += layout.ascender;
881 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " pen y %f\n", penY );
883 bool ellipsis = false;
884 if( mEllipsisEnabled )
886 // Does the ellipsis of the last line.
887 ellipsis = EllipsisLine( layoutParameters,
891 glyphPositionsBuffer,
894 currentParagraphDirection );
899 // No more lines to layout.
904 // Whether the last line has been laid-out.
905 const bool isLastLine = index + layout.numberOfGlyphs == layoutParameters.totalNumberOfGlyphs;
907 if( numberOfLines == linesCapacity )
909 // Reserve more space for the next lines.
910 linesBuffer = ResizeLinesBuffer( lines,
913 updateCurrentBuffer );
916 // Updates the current text's layout with the line's layout.
917 UpdateTextLayout( layoutParameters,
925 const GlyphIndex nextIndex = index + layout.numberOfGlyphs;
927 if( ( nextIndex == layoutParameters.totalNumberOfGlyphs ) &&
928 layoutParameters.isLastNewParagraph &&
929 ( mLayout == MULTI_LINE_BOX ) )
931 // The last character of the text is a new paragraph character.
932 // An extra line with no characters is added to increase the text's height
933 // in order to place the cursor.
935 if( numberOfLines == linesCapacity )
937 // Reserve more space for the next lines.
938 linesBuffer = ResizeLinesBuffer( lines,
941 updateCurrentBuffer );
944 UpdateTextLayout( layoutParameters,
945 layout.characterIndex + layout.numberOfCharacters,
946 index + layout.numberOfGlyphs,
950 } // whether to add a last line.
952 // Sets the positions of the glyphs.
953 SetGlyphPositions( layoutParameters.glyphsBuffer + index,
954 layout.numberOfGlyphs,
955 glyphPositionsBuffer + index - layoutParameters.startGlyphIndex );
957 // Updates the vertical pen's position.
958 penY += -layout.descender;
960 // Increase the glyph index.
963 } // end for() traversing glyphs.
965 if( updateCurrentBuffer )
967 glyphPositions.Insert( glyphPositions.Begin() + layoutParameters.startGlyphIndex,
968 newGlyphPositions.Begin(),
969 newGlyphPositions.End() );
970 glyphPositions.Resize( layoutParameters.totalNumberOfGlyphs );
972 newLines.Resize( numberOfLines );
974 // Current text's layout size adds only the newly laid-out lines.
975 // Updates the layout size with the previously laid-out lines.
976 UpdateLayoutSize( lines,
979 if( 0u != newLines.Count() )
981 const LineRun& lastLine = *( newLines.End() - 1u );
983 const Length characterOffset = lastLine.characterRun.characterIndex + lastLine.characterRun.numberOfCharacters;
984 const Length glyphOffset = lastLine.glyphRun.glyphIndex + lastLine.glyphRun.numberOfGlyphs;
986 // Update the indices of the runs before the new laid-out lines are inserted.
987 UpdateLineIndexOffsets( layoutParameters,
993 lines.Insert( lines.Begin() + layoutParameters.startLineIndex,
1000 lines.Resize( numberOfLines );
1003 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--LayoutText\n\n" );
1008 void ReLayoutRightToLeftLines( const LayoutParameters& layoutParameters,
1009 CharacterIndex startIndex,
1010 Length numberOfCharacters,
1011 Vector<Vector2>& glyphPositions )
1013 const CharacterIndex lastCharacterIndex = startIndex + numberOfCharacters;
1015 // Traverses the paragraphs with right to left characters.
1016 for( LineIndex lineIndex = 0u; lineIndex < layoutParameters.numberOfBidirectionalInfoRuns; ++lineIndex )
1018 const BidirectionalLineInfoRun& bidiLine = *( layoutParameters.lineBidirectionalInfoRunsBuffer + lineIndex );
1020 if( startIndex >= bidiLine.characterRun.characterIndex + bidiLine.characterRun.numberOfCharacters )
1022 // Do not reorder the line if it has been already reordered.
1026 if( bidiLine.characterRun.characterIndex >= lastCharacterIndex )
1028 // Do not reorder the lines after the last requested character.
1032 const CharacterIndex characterVisualIndex = bidiLine.characterRun.characterIndex + *bidiLine.visualToLogicalMap;
1033 const GlyphInfo& glyph = *( layoutParameters.glyphsBuffer + *( layoutParameters.charactersToGlyphsBuffer + characterVisualIndex ) );
1035 float penX = ( 0.f > glyph.xBearing ) ? -glyph.xBearing : 0.f;
1037 Vector2* glyphPositionsBuffer = glyphPositions.Begin();
1039 // Traverses the characters of the right to left paragraph.
1040 for( CharacterIndex characterLogicalIndex = 0u;
1041 characterLogicalIndex < bidiLine.characterRun.numberOfCharacters;
1042 ++characterLogicalIndex )
1044 // Convert the character in the logical order into the character in the visual order.
1045 const CharacterIndex characterVisualIndex = bidiLine.characterRun.characterIndex + *( bidiLine.visualToLogicalMap + characterLogicalIndex );
1047 // Get the number of glyphs of the character.
1048 const Length numberOfGlyphs = *( layoutParameters.glyphsPerCharacterBuffer + characterVisualIndex );
1050 for( GlyphIndex index = 0u; index < numberOfGlyphs; ++index )
1052 // Convert the character in the visual order into the glyph in the visual order.
1053 const GlyphIndex glyphIndex = *( layoutParameters.charactersToGlyphsBuffer + characterVisualIndex ) + index;
1055 DALI_ASSERT_DEBUG( 0u <= glyphIndex && glyphIndex < layoutParameters.totalNumberOfGlyphs );
1057 const GlyphInfo& glyph = *( layoutParameters.glyphsBuffer + glyphIndex );
1058 Vector2& position = *( glyphPositionsBuffer + glyphIndex );
1060 position.x = penX + glyph.xBearing;
1061 penX += glyph.advance;
1067 void Align( const Size& size,
1068 CharacterIndex startIndex,
1069 Length numberOfCharacters,
1070 Vector<LineRun>& lines )
1072 const CharacterIndex lastCharacterPlusOne = startIndex + numberOfCharacters;
1074 // Traverse all lines and align the glyphs.
1075 for( Vector<LineRun>::Iterator it = lines.Begin(), endIt = lines.End();
1079 LineRun& line = *it;
1081 if( line.characterRun.characterIndex < startIndex )
1083 // Do not align lines which have already been aligned.
1087 if( line.characterRun.characterIndex >= lastCharacterPlusOne )
1089 // Do not align lines beyond the last laid-out character.
1093 // Calculate the line's alignment offset accordingly with the align option,
1094 // the box width, line length, and the paragraph's direction.
1095 CalculateHorizontalAlignment( size.width,
1100 void CalculateHorizontalAlignment( float boxWidth,
1103 line.alignmentOffset = 0.f;
1104 const bool isRTL = RTL == line.direction;
1105 float lineLength = line.width;
1107 HorizontalAlignment alignment = mHorizontalAlignment;
1110 // Swap the alignment type if the line is right to left.
1113 case HORIZONTAL_ALIGN_BEGIN:
1115 alignment = HORIZONTAL_ALIGN_END;
1118 case HORIZONTAL_ALIGN_CENTER:
1123 case HORIZONTAL_ALIGN_END:
1125 alignment = HORIZONTAL_ALIGN_BEGIN;
1131 // Calculate the horizontal line offset.
1134 case HORIZONTAL_ALIGN_BEGIN:
1136 line.alignmentOffset = 0.f;
1140 // 'Remove' the white spaces at the end of the line (which are at the beginning in visual order)
1141 line.alignmentOffset -= line.extraLength;
1145 case HORIZONTAL_ALIGN_CENTER:
1147 line.alignmentOffset = 0.5f * ( boxWidth - lineLength );
1151 line.alignmentOffset -= line.extraLength;
1154 line.alignmentOffset = floorf( line.alignmentOffset ); // try to avoid pixel alignment.
1157 case HORIZONTAL_ALIGN_END:
1161 lineLength += line.extraLength;
1164 line.alignmentOffset = boxWidth - lineLength;
1170 void Initialize( LineRun& line )
1172 line.glyphRun.glyphIndex = 0u;
1173 line.glyphRun.numberOfGlyphs = 0u;
1174 line.characterRun.characterIndex = 0u;
1175 line.characterRun.numberOfCharacters = 0u;
1177 line.ascender = 0.f;
1178 line.descender = 0.f;
1179 line.extraLength = 0.f;
1180 line.alignmentOffset = 0.f;
1181 line.direction = !RTL;
1182 line.ellipsis = false;
1185 LayoutEngine::Layout mLayout;
1186 LayoutEngine::HorizontalAlignment mHorizontalAlignment;
1187 LayoutEngine::VerticalAlignment mVerticalAlignment;
1190 IntrusivePtr<Metrics> mMetrics;
1192 bool mEllipsisEnabled:1;
1195 LayoutEngine::LayoutEngine()
1198 mImpl = new LayoutEngine::Impl();
1201 LayoutEngine::~LayoutEngine()
1206 void LayoutEngine::SetMetrics( MetricsPtr& metrics )
1208 mImpl->mMetrics = metrics;
1211 void LayoutEngine::SetLayout( Layout layout )
1213 mImpl->mLayout = layout;
1216 LayoutEngine::Layout LayoutEngine::GetLayout() const
1218 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "GetLayout[%d]\n", mImpl->mLayout);
1219 return mImpl->mLayout;
1222 void LayoutEngine::SetTextEllipsisEnabled( bool enabled )
1224 DALI_LOG_INFO( gLogFilter, Debug::General, "-->LayoutEngine::SetTextEllipsisEnabled[%s]\n", (enabled)?"true":"false" );
1225 mImpl->mEllipsisEnabled = enabled;
1228 bool LayoutEngine::GetTextEllipsisEnabled() const
1230 return mImpl->mEllipsisEnabled;
1233 void LayoutEngine::SetHorizontalAlignment( HorizontalAlignment alignment )
1235 mImpl->mHorizontalAlignment = alignment;
1238 LayoutEngine::HorizontalAlignment LayoutEngine::GetHorizontalAlignment() const
1240 return mImpl->mHorizontalAlignment;
1243 void LayoutEngine::SetVerticalAlignment( VerticalAlignment alignment )
1245 mImpl->mVerticalAlignment = alignment;
1248 LayoutEngine::VerticalAlignment LayoutEngine::GetVerticalAlignment() const
1250 return mImpl->mVerticalAlignment;
1253 void LayoutEngine::SetCursorWidth( int width )
1255 mImpl->mCursorWidth = static_cast<float>( width );
1258 int LayoutEngine::GetCursorWidth() const
1260 return static_cast<int>( mImpl->mCursorWidth );
1263 bool LayoutEngine::LayoutText( const LayoutParameters& layoutParameters,
1264 Vector<Vector2>& glyphPositions,
1265 Vector<LineRun>& lines,
1268 return mImpl->LayoutText( layoutParameters,
1274 void LayoutEngine::ReLayoutRightToLeftLines( const LayoutParameters& layoutParameters,
1275 CharacterIndex startIndex,
1276 Length numberOfCharacters,
1277 Vector<Vector2>& glyphPositions )
1279 mImpl->ReLayoutRightToLeftLines( layoutParameters,
1285 void LayoutEngine::Align( const Size& size,
1286 CharacterIndex startIndex,
1287 Length numberOfCharacters,
1288 Vector<LineRun>& lines )
1298 } // namespace Toolkit