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>
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>
47 #if defined(DEBUG_ENABLED)
48 Debug::Filter* gLogFilter = Debug::Filter::New(Debug::Concise, true, "LOG_TEXT_LAYOUT");
51 const float MAX_FLOAT = std::numeric_limits<float>::max();
52 const bool RTL = true;
53 const float CURSOR_WIDTH = 1.f;
54 const float LINE_SPACING= 0.f;
59 * @brief Stores temporary layout info of the line.
67 numberOfCharacters( 0u ),
71 wsLengthEndOfLine( 0.f ),
73 descender( MAX_FLOAT ),
85 numberOfCharacters = 0u;
89 wsLengthEndOfLine = 0.f;
91 descender = MAX_FLOAT;
94 GlyphIndex glyphIndex; ///< Index of the first glyph to be laid-out.
95 CharacterIndex characterIndex; ///< Index of the first character to be laid-out.
96 Length numberOfGlyphs; ///< The number of glyph which fit in one line.
97 Length numberOfCharacters; ///< The number of characters which fit in one line.
98 float length; ///< The addition of the advance metric of all the glyphs which fit in one line.
99 float extraBearing; ///< The extra width to be added to the line's length when the bearing of the first glyph is negative.
100 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.
101 float wsLengthEndOfLine; ///< The length of the white spaces at the end of the line.
102 float ascender; ///< The maximum ascender of all fonts in the line.
103 float descender; ///< The minimum descender of all fonts in the line.
104 float lineSpacing; ///< The line spacing
110 : mLayout( Layout::Engine::SINGLE_LINE_BOX ),
111 mCursorWidth( CURSOR_WIDTH ),
112 mDefaultLineSpacing( LINE_SPACING ),
113 mPreviousCharacterExtraWidth( 0.0f )
118 * @brief Updates the line ascender and descender with the metrics of a new font.
120 * @param[in] fontId The id of the new font.
121 * @param[in,out] lineLayout The line layout.
123 void UpdateLineHeight( FontId fontId, LineLayout& lineLayout )
125 Text::FontMetrics fontMetrics;
126 mMetrics->GetFontMetrics( fontId, fontMetrics );
128 // Sets the maximum ascender.
129 if( fontMetrics.ascender > lineLayout.ascender )
131 lineLayout.ascender = fontMetrics.ascender;
134 // Sets the minimum descender.
135 if( fontMetrics.descender < lineLayout.descender )
137 lineLayout.descender = fontMetrics.descender;
140 // set the line spacing
141 lineLayout.lineSpacing = mDefaultLineSpacing;
145 * @brief Merges a temporary line layout into the line layout.
147 * @param[in,out] lineLayout The line layout.
148 * @param[in] tmpLineLayout A temporary line layout.
150 void MergeLineLayout( LineLayout& lineLayout,
151 const LineLayout& tmpLineLayout )
153 lineLayout.numberOfCharacters += tmpLineLayout.numberOfCharacters;
154 lineLayout.numberOfGlyphs += tmpLineLayout.numberOfGlyphs;
155 lineLayout.length += tmpLineLayout.length;
157 if( 0.f < tmpLineLayout.length )
159 lineLayout.length += lineLayout.wsLengthEndOfLine;
161 lineLayout.wsLengthEndOfLine = tmpLineLayout.wsLengthEndOfLine;
165 lineLayout.wsLengthEndOfLine += tmpLineLayout.wsLengthEndOfLine;
168 if( tmpLineLayout.ascender > lineLayout.ascender )
170 lineLayout.ascender = tmpLineLayout.ascender;
173 if( tmpLineLayout.descender < lineLayout.descender )
175 lineLayout.descender = tmpLineLayout.descender;
180 * Retrieves the line layout for a given box width.
182 * @note This method lais out text as it were left to right. At this point is not possible to reorder the line
183 * because the number of characters of the line is not known (one of the responsabilities of this method
184 * is calculate that). Due to glyph's 'x' bearing, width and advance, when right to left or mixed right to left
185 * and left to right text is laid-out, it can be small differences in the line length. One solution is to
186 * reorder and re-lay out the text after this method and add or remove one extra glyph if needed. However,
187 * this method calculates which are the first and last glyphs of the line (the ones that causes the
188 * differences). This is a good point to check if there is problems with the text exceeding the boundaries
189 * of the control when there is right to left text.
191 * @param[in] parameters The layout parameters.
192 * @param[out] lineLayout The line layout.
193 * @param[in,out] paragraphDirection in: the current paragraph's direction, out: the next paragraph's direction. Is set after a must break.
194 * @param[in] completelyFill Whether to completely fill the line ( even if the last word exceeds the boundaries ).
196 void GetLineLayoutForBox( const Parameters& parameters,
197 LineLayout& lineLayout,
198 CharacterDirection& paragraphDirection,
199 bool completelyFill )
201 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "-->GetLineLayoutForBox\n" );
202 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " initial glyph index : %d\n", lineLayout.glyphIndex );
203 // Stores temporary line layout which has not been added to the final line layout.
204 LineLayout tmpLineLayout;
206 const bool isMultiline = mLayout == MULTI_LINE_BOX;
207 const bool isWordLaidOut = parameters.lineWrapMode == Text::LineWrap::WORD;
209 // The last glyph to be laid-out.
210 const GlyphIndex lastGlyphOfParagraphPlusOne = parameters.startGlyphIndex + parameters.numberOfGlyphs;
212 // If the first glyph has a negative bearing its absolute value needs to be added to the line length.
213 // In the case the line starts with a right to left character, if the width is longer than the advance,
214 // the difference needs to be added to the line length.
216 // Check whether the first glyph comes from a character that is shaped in multiple glyphs.
217 const Length numberOfGLyphsInGroup = GetNumberOfGlyphsOfGroup( lineLayout.glyphIndex,
218 lastGlyphOfParagraphPlusOne,
219 parameters.charactersPerGlyphBuffer );
221 GlyphMetrics glyphMetrics;
222 GetGlyphsMetrics( lineLayout.glyphIndex,
223 numberOfGLyphsInGroup,
225 parameters.glyphsBuffer,
228 // Set the direction of the first character of the line.
229 lineLayout.characterIndex = *( parameters.glyphsToCharactersBuffer + lineLayout.glyphIndex );
230 const CharacterDirection firstCharacterDirection = ( NULL == parameters.characterDirectionBuffer ) ? false : *( parameters.characterDirectionBuffer + lineLayout.characterIndex );
231 CharacterDirection previousCharacterDirection = firstCharacterDirection;
233 const float extraWidth = glyphMetrics.xBearing + glyphMetrics.width - glyphMetrics.advance;
234 float tmpExtraWidth = ( 0.f < extraWidth ) ? extraWidth : 0.f;
236 float tmpExtraBearing = ( 0.f > glyphMetrics.xBearing ) ? -glyphMetrics.xBearing : 0.f;
238 tmpLineLayout.length += mCursorWidth; // Added to give some space to the cursor.
240 // Calculate the line height if there is no characters.
241 FontId lastFontId = glyphMetrics.fontId;
242 UpdateLineHeight( lastFontId, tmpLineLayout );
244 bool oneWordLaidOut = false;
246 for( GlyphIndex glyphIndex = lineLayout.glyphIndex;
247 glyphIndex < lastGlyphOfParagraphPlusOne; )
249 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " glyph index : %d\n", glyphIndex );
251 // Check whether this glyph comes from a character that is shaped in multiple glyphs.
252 const Length numberOfGLyphsInGroup = GetNumberOfGlyphsOfGroup( glyphIndex,
253 lastGlyphOfParagraphPlusOne,
254 parameters.charactersPerGlyphBuffer );
256 GlyphMetrics glyphMetrics;
257 GetGlyphsMetrics( glyphIndex,
258 numberOfGLyphsInGroup,
260 parameters.glyphsBuffer,
263 const bool isLastGlyph = glyphIndex + numberOfGLyphsInGroup == parameters.totalNumberOfGlyphs;
265 // Check if the font of the current glyph is the same of the previous one.
266 // If it's different the ascender and descender need to be updated.
267 if( lastFontId != glyphMetrics.fontId )
269 UpdateLineHeight( glyphMetrics.fontId, tmpLineLayout );
270 lastFontId = glyphMetrics.fontId;
273 // Get the character indices for the current glyph. The last character index is needed
274 // because there are glyphs formed by more than one character but their break info is
275 // given only for the last character.
276 const Length charactersPerGlyph = *( parameters.charactersPerGlyphBuffer + glyphIndex + numberOfGLyphsInGroup - 1u );
277 const bool hasCharacters = charactersPerGlyph > 0u;
278 const CharacterIndex characterFirstIndex = *( parameters.glyphsToCharactersBuffer + glyphIndex );
279 const CharacterIndex characterLastIndex = characterFirstIndex + ( hasCharacters ? charactersPerGlyph - 1u : 0u );
281 // Get the line break info for the current character.
282 const LineBreakInfo lineBreakInfo = hasCharacters ? *( parameters.lineBreakInfoBuffer + characterLastIndex ) : TextAbstraction::LINE_NO_BREAK;
284 // Increase the number of characters.
285 tmpLineLayout.numberOfCharacters += charactersPerGlyph;
287 // Increase the number of glyphs.
288 tmpLineLayout.numberOfGlyphs += numberOfGLyphsInGroup;
290 // Check whether is a white space.
291 const Character character = *( parameters.textBuffer + characterFirstIndex );
292 const bool isWhiteSpace = TextAbstraction::IsWhiteSpace( character );
294 // Used to restore the temporal line layout when a single word does not fit in the control's width and is split by character.
295 const float previousTmpLineLength = tmpLineLayout.length;
296 const float previousTmpExtraBearing = tmpExtraBearing;
297 const float previousTmpExtraWidth = tmpExtraWidth;
299 // Get the character's direction.
300 const CharacterDirection characterDirection = ( NULL == parameters.characterDirectionBuffer ) ? false : *( parameters.characterDirectionBuffer + characterFirstIndex );
302 // Increase the accumulated length.
305 // Add the length to the length of white spaces at the end of the line.
306 tmpLineLayout.wsLengthEndOfLine += glyphMetrics.advance; // The advance is used as the width is always zero for the white spaces.
310 // Add as well any previous white space length.
311 tmpLineLayout.length += tmpLineLayout.wsLengthEndOfLine + glyphMetrics.advance;
313 // An extra space may be added to the line for the first and last glyph of the line.
314 // If the bearing of the first glyph is negative, its positive value needs to be added.
315 // If the bearing plus the width of the last glyph is greater than the advance, the difference
316 // needs to be added.
318 if( characterDirection == paragraphDirection )
320 if( RTL == characterDirection )
331 tmpExtraBearing = ( 0.f > glyphMetrics.xBearing ) ? -glyphMetrics.xBearing : 0.f;
344 const float extraWidth = glyphMetrics.xBearing + glyphMetrics.width - glyphMetrics.advance;
345 tmpExtraWidth = ( 0.f < extraWidth ) ? extraWidth : 0.f;
346 tmpExtraWidth = std::max( mPreviousCharacterExtraWidth - glyphMetrics.advance, tmpExtraWidth );
351 if( characterDirection != previousCharacterDirection )
353 if( RTL == characterDirection )
358 const float extraWidth = glyphMetrics.xBearing + glyphMetrics.width - glyphMetrics.advance;
359 tmpExtraWidth = ( 0.f < extraWidth ) ? extraWidth : 0.f;
360 tmpExtraWidth = std::max( mPreviousCharacterExtraWidth - glyphMetrics.advance, tmpExtraWidth );
367 tmpExtraBearing = ( 0.f > glyphMetrics.xBearing ) ? -glyphMetrics.xBearing : 0.f;
370 else if( characterDirection == firstCharacterDirection )
372 if( RTL == characterDirection )
378 tmpExtraBearing = ( 0.f > glyphMetrics.xBearing ) ? -glyphMetrics.xBearing : 0.f;
386 const float extraWidth = glyphMetrics.xBearing + glyphMetrics.width - glyphMetrics.advance;
387 tmpExtraWidth = ( 0.f < extraWidth ) ? extraWidth : 0.f;
388 tmpExtraWidth = std::max( mPreviousCharacterExtraWidth - glyphMetrics.advance, tmpExtraWidth );
393 // Clear the white space length at the end of the line.
394 tmpLineLayout.wsLengthEndOfLine = 0.f;
397 // If calculation is end but wsLengthEndOfLine is exist, it means end of text is space.
398 // Merge remained length.
399 if ( !parameters.ignoreSpaceAfterText && glyphIndex == lastGlyphOfParagraphPlusOne-1 && tmpLineLayout.wsLengthEndOfLine > 0 )
401 tmpLineLayout.length += tmpLineLayout.wsLengthEndOfLine;
402 tmpLineLayout.wsLengthEndOfLine = 0u;
405 // Save the current extra width to compare with the next one
406 mPreviousCharacterExtraWidth = tmpExtraWidth;
408 // Check if the accumulated length fits in the width of the box.
409 if( ( completelyFill || isMultiline ) && !(parameters.ignoreSpaceAfterText && isWhiteSpace) &&
410 ( tmpExtraBearing + lineLayout.length + lineLayout.wsLengthEndOfLine + tmpLineLayout.length + tmpExtraWidth > parameters.boundingBox.width ) )
412 // Current word does not fit in the box's width.
413 if( !oneWordLaidOut || completelyFill )
415 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " Break the word by character\n" );
417 // The word doesn't fit in the control's width. It needs to be split by character.
418 if( tmpLineLayout.numberOfGlyphs > 0u )
420 tmpLineLayout.numberOfCharacters -= charactersPerGlyph;
421 tmpLineLayout.numberOfGlyphs -= numberOfGLyphsInGroup;
422 tmpLineLayout.length = previousTmpLineLength;
423 tmpExtraBearing = previousTmpExtraBearing;
424 tmpExtraWidth = previousTmpExtraWidth;
427 // Add part of the word to the line layout.
428 MergeLineLayout( lineLayout, tmpLineLayout );
432 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " Current word does not fit.\n" );
435 lineLayout.extraBearing = tmpExtraBearing;
436 lineLayout.extraWidth = tmpExtraWidth;
438 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--GetLineLayoutForBox.\n" );
443 if( ( isMultiline || isLastGlyph ) &&
444 ( TextAbstraction::LINE_MUST_BREAK == lineBreakInfo ) )
446 // Must break the line. Update the line layout and return.
447 MergeLineLayout( lineLayout, tmpLineLayout );
449 // Set the next paragraph's direction.
451 ( NULL != parameters.characterDirectionBuffer ) )
453 paragraphDirection = *( parameters.characterDirectionBuffer + 1u + characterLastIndex );
456 lineLayout.extraBearing = tmpExtraBearing;
457 lineLayout.extraWidth = tmpExtraWidth;
459 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " Must break\n" );
460 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--GetLineLayoutForBox\n" );
466 ( TextAbstraction::LINE_ALLOW_BREAK == lineBreakInfo ) )
468 oneWordLaidOut = isWordLaidOut;
469 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " One word laid-out\n" );
471 // Current glyph is the last one of the current word.
472 // Add the temporal layout to the current one.
473 MergeLineLayout( lineLayout, tmpLineLayout );
475 tmpLineLayout.Clear();
478 previousCharacterDirection = characterDirection;
479 glyphIndex += numberOfGLyphsInGroup;
482 lineLayout.extraBearing = tmpExtraBearing;
483 lineLayout.extraWidth = tmpExtraWidth;
485 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--GetLineLayoutForBox\n" );
488 void SetGlyphPositions( const GlyphInfo* const glyphsBuffer,
489 Length numberOfGlyphs,
491 Vector2* glyphPositionsBuffer )
493 // Traverse the glyphs and set the positions.
495 // Check if the x bearing of the first character is negative.
496 // If it has a negative x bearing, it will exceed the boundaries of the actor,
497 // so the penX position needs to be moved to the right.
499 const GlyphInfo& glyph = *glyphsBuffer;
500 float penX = ( 0.f > glyph.xBearing ) ? -glyph.xBearing + outlineWidth : outlineWidth;
503 for( GlyphIndex i = 0u; i < numberOfGlyphs; ++i )
505 const GlyphInfo& glyph = *( glyphsBuffer + i );
506 Vector2& position = *( glyphPositionsBuffer + i );
508 position.x = penX + glyph.xBearing;
509 position.y = -glyph.yBearing;
511 penX += glyph.advance;
516 * @brief Resizes the line buffer.
518 * @param[in,out] lines The vector of lines. Used when the layout is created from scratch.
519 * @param[in,out] newLines The vector of lines used instead of @p lines when the layout is updated.
520 * @param[in,out] linesCapacity The capacity of the vector (either lines or newLines).
521 * @param[in] updateCurrentBuffer Whether the layout is updated.
523 * @return Pointer to either lines or newLines.
525 LineRun* ResizeLinesBuffer( Vector<LineRun>& lines,
526 Vector<LineRun>& newLines,
527 Length& linesCapacity,
528 bool updateCurrentBuffer )
530 LineRun* linesBuffer = NULL;
531 // Reserve more space for the next lines.
533 if( updateCurrentBuffer )
535 newLines.Resize( linesCapacity );
536 linesBuffer = newLines.Begin();
540 lines.Resize( linesCapacity );
541 linesBuffer = lines.Begin();
548 * Ellipsis a line if it exceeds the width's of the bounding box.
550 * @param[in] layoutParameters The parameters needed to layout the text.
551 * @param[in] layout The line layout.
552 * @param[in,out] layoutSize The text's layout size.
553 * @param[in,out] linesBuffer Pointer to the line's buffer.
554 * @param[in,out] glyphPositionsBuffer Pointer to the position's buffer.
555 * @param[in,out] numberOfLines The number of laid-out lines.
556 * @param[in] penY The vertical layout position.
557 * @param[in] currentParagraphDirection The current paragraph's direction.
558 * @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
560 * return Whether the line is ellipsized.
562 bool EllipsisLine( const Parameters& layoutParameters,
563 const LineLayout& layout,
565 LineRun* linesBuffer,
566 Vector2* glyphPositionsBuffer,
567 Length& numberOfLines,
569 CharacterDirection currentParagraphDirection,
570 bool& isAutoScrollEnabled )
572 const bool ellipsis = isAutoScrollEnabled ? ( penY - layout.descender > layoutParameters.boundingBox.height ) :
573 ( ( penY - layout.descender > layoutParameters.boundingBox.height ) ||
574 ( ( mLayout == SINGLE_LINE_BOX ) &&
575 ( layout.extraBearing + layout.length + layout.extraWidth > layoutParameters.boundingBox.width ) ) );
579 isAutoScrollEnabled = false;
580 // Do not layout more lines if ellipsis is enabled.
582 // The last line needs to be completely filled with characters.
583 // Part of a word may be used.
585 LineRun* lineRun = NULL;
586 LineLayout ellipsisLayout;
587 if( 0u != numberOfLines )
589 // Get the last line and layout it again with the 'completelyFill' flag to true.
590 lineRun = linesBuffer + ( numberOfLines - 1u );
592 penY -= layout.ascender - lineRun->descender + lineRun->lineSpacing;
594 ellipsisLayout.glyphIndex = lineRun->glyphRun.glyphIndex;
598 // At least there is space reserved for one line.
599 lineRun = linesBuffer;
601 lineRun->glyphRun.glyphIndex = 0u;
602 ellipsisLayout.glyphIndex = 0u;
607 GetLineLayoutForBox( layoutParameters,
609 currentParagraphDirection,
612 lineRun->glyphRun.numberOfGlyphs = ellipsisLayout.numberOfGlyphs;
613 lineRun->characterRun.characterIndex = ellipsisLayout.characterIndex;
614 lineRun->characterRun.numberOfCharacters = ellipsisLayout.numberOfCharacters;
615 lineRun->width = ellipsisLayout.length;
616 lineRun->extraLength = ( ellipsisLayout.wsLengthEndOfLine > 0.f ) ? ellipsisLayout.wsLengthEndOfLine - ellipsisLayout.extraWidth : 0.f;
617 lineRun->ascender = ellipsisLayout.ascender;
618 lineRun->descender = ellipsisLayout.descender;
619 lineRun->direction = !RTL;
620 lineRun->ellipsis = true;
622 layoutSize.width = layoutParameters.boundingBox.width;
623 if( layoutSize.height < Math::MACHINE_EPSILON_1000 )
625 layoutSize.height += ( lineRun->ascender + -lineRun->descender ) + lineRun->lineSpacing;
628 SetGlyphPositions( layoutParameters.glyphsBuffer + lineRun->glyphRun.glyphIndex,
629 ellipsisLayout.numberOfGlyphs,
630 layoutParameters.outlineWidth,
631 glyphPositionsBuffer + lineRun->glyphRun.glyphIndex - layoutParameters.startGlyphIndex );
638 * @brief Updates the text layout with a new laid-out line.
640 * @param[in] layoutParameters The parameters needed to layout the text.
641 * @param[in] layout The line layout.
642 * @param[in,out] layoutSize The text's layout size.
643 * @param[in,out] linesBuffer Pointer to the line's buffer.
644 * @param[in] index Index to the vector of glyphs.
645 * @param[in,out] numberOfLines The number of laid-out lines.
646 * @param[in] isLastLine Whether the laid-out line is the last one.
648 void UpdateTextLayout( const Parameters& layoutParameters,
649 const LineLayout& layout,
651 LineRun* linesBuffer,
653 Length& numberOfLines,
656 LineRun& lineRun = *( linesBuffer + numberOfLines );
659 lineRun.glyphRun.glyphIndex = index;
660 lineRun.glyphRun.numberOfGlyphs = layout.numberOfGlyphs;
661 lineRun.characterRun.characterIndex = layout.characterIndex;
662 lineRun.characterRun.numberOfCharacters = layout.numberOfCharacters;
663 lineRun.lineSpacing = mDefaultLineSpacing;
665 if( isLastLine && !layoutParameters.isLastNewParagraph )
667 const float width = layout.extraBearing + layout.length + layout.extraWidth + layout.wsLengthEndOfLine;
668 if( MULTI_LINE_BOX == mLayout )
670 lineRun.width = ( width > layoutParameters.boundingBox.width ) ? layoutParameters.boundingBox.width : width;
674 lineRun.width = width;
677 lineRun.extraLength = 0.f;
681 lineRun.width = layout.extraBearing + layout.length + layout.extraWidth;
682 lineRun.extraLength = ( layout.wsLengthEndOfLine > 0.f ) ? layout.wsLengthEndOfLine - layout.extraWidth : 0.f;
684 lineRun.ascender = layout.ascender;
685 lineRun.descender = layout.descender;
686 lineRun.direction = !RTL;
687 lineRun.ellipsis = false;
689 // Update the actual size.
690 if( lineRun.width > layoutSize.width )
692 layoutSize.width = lineRun.width;
695 layoutSize.height += ( lineRun.ascender + -lineRun.descender ) + lineRun.lineSpacing;
699 * @brief Updates the text layout with the last laid-out line.
701 * @param[in] layoutParameters The parameters needed to layout the text.
702 * @param[in] characterIndex The character index of the line.
703 * @param[in] glyphIndex The glyph index of the line.
704 * @param[in,out] layoutSize The text's layout size.
705 * @param[in,out] linesBuffer Pointer to the line's buffer.
706 * @param[in,out] numberOfLines The number of laid-out lines.
708 void UpdateTextLayout( const Parameters& layoutParameters,
709 CharacterIndex characterIndex,
710 GlyphIndex glyphIndex,
712 LineRun* linesBuffer,
713 Length& numberOfLines )
715 // Need to add a new line with no characters but with height to increase the layoutSize.height
716 const GlyphInfo& glyphInfo = *( layoutParameters.glyphsBuffer + layoutParameters.totalNumberOfGlyphs - 1u );
718 Text::FontMetrics fontMetrics;
719 mMetrics->GetFontMetrics( glyphInfo.fontId, fontMetrics );
721 LineRun& lineRun = *( linesBuffer + numberOfLines );
724 lineRun.glyphRun.glyphIndex = glyphIndex;
725 lineRun.glyphRun.numberOfGlyphs = 0u;
726 lineRun.characterRun.characterIndex = characterIndex;
727 lineRun.characterRun.numberOfCharacters = 0u;
729 lineRun.ascender = fontMetrics.ascender;
730 lineRun.descender = fontMetrics.descender;
731 lineRun.extraLength = 0.f;
732 lineRun.alignmentOffset = 0.f;
733 lineRun.direction = !RTL;
734 lineRun.ellipsis = false;
735 lineRun.lineSpacing = mDefaultLineSpacing;
737 layoutSize.height += ( lineRun.ascender + -lineRun.descender ) + lineRun.lineSpacing;
741 * @brief Updates the text's layout size adding the size of the previously laid-out lines.
743 * @param[in] lines The vector of lines (before the new laid-out lines are inserted).
744 * @param[in,out] layoutSize The text's layout size.
746 void UpdateLayoutSize( const Vector<LineRun>& lines,
749 for( Vector<LineRun>::ConstIterator it = lines.Begin(),
754 const LineRun& line = *it;
756 if( line.width > layoutSize.width )
758 layoutSize.width = line.width;
761 layoutSize.height += ( line.ascender + -line.descender ) + line.lineSpacing;
766 * @brief Updates the indices of the character and glyph runs of the lines before the new lines are inserted.
768 * @param[in] layoutParameters The parameters needed to layout the text.
769 * @param[in,out] lines The vector of lines (before the new laid-out lines are inserted).
770 * @param[in] characterOffset The offset to be added to the runs of characters.
771 * @param[in] glyphOffset The offset to be added to the runs of glyphs.
773 void UpdateLineIndexOffsets( const Parameters& layoutParameters,
774 Vector<LineRun>& lines,
775 Length characterOffset,
778 // Update the glyph and character runs.
779 for( Vector<LineRun>::Iterator it = lines.Begin() + layoutParameters.startLineIndex,
786 line.glyphRun.glyphIndex = glyphOffset;
787 line.characterRun.characterIndex = characterOffset;
789 glyphOffset += line.glyphRun.numberOfGlyphs;
790 characterOffset += line.characterRun.numberOfCharacters;
794 bool LayoutText( const Parameters& layoutParameters,
795 Vector<Vector2>& glyphPositions,
796 Vector<LineRun>& lines,
798 bool elideTextEnabled,
799 bool& isAutoScrollEnabled )
801 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "-->LayoutText\n" );
802 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " box size %f, %f\n", layoutParameters.boundingBox.width, layoutParameters.boundingBox.height );
804 if( 0u == layoutParameters.numberOfGlyphs )
806 // Add an extra line if the last character is a new paragraph character and the last line doesn't have zero characters.
807 if( layoutParameters.isLastNewParagraph )
809 Length numberOfLines = lines.Count();
810 if( 0u != numberOfLines )
812 const LineRun& lastLine = *( lines.End() - 1u );
814 if( 0u != lastLine.characterRun.numberOfCharacters )
816 // Need to add a new line with no characters but with height to increase the layoutSize.height
818 Initialize( newLine );
819 lines.PushBack( newLine );
821 UpdateTextLayout( layoutParameters,
822 lastLine.characterRun.characterIndex + lastLine.characterRun.numberOfCharacters,
823 lastLine.glyphRun.glyphIndex + lastLine.glyphRun.numberOfGlyphs,
831 // Calculates the layout size.
832 UpdateLayoutSize( lines,
835 // Nothing else do if there are no glyphs to layout.
839 const GlyphIndex lastGlyphPlusOne = layoutParameters.startGlyphIndex + layoutParameters.numberOfGlyphs;
841 // In a previous layout, an extra line with no characters may have been added if the text ended with a new paragraph character.
842 // This extra line needs to be removed.
843 if( 0u != lines.Count() )
845 Vector<LineRun>::Iterator lastLine = lines.End() - 1u;
847 if( ( 0u == lastLine->characterRun.numberOfCharacters ) &&
848 ( lastGlyphPlusOne == layoutParameters.totalNumberOfGlyphs ) )
850 lines.Remove( lastLine );
854 // Set the first paragraph's direction.
855 CharacterDirection paragraphDirection = ( NULL != layoutParameters.characterDirectionBuffer ) ? *layoutParameters.characterDirectionBuffer : !RTL;
857 // Whether the layout is being updated or set from scratch.
858 const bool updateCurrentBuffer = layoutParameters.numberOfGlyphs < layoutParameters.totalNumberOfGlyphs;
860 Vector2* glyphPositionsBuffer = NULL;
861 Vector<Vector2> newGlyphPositions;
863 LineRun* linesBuffer = NULL;
864 Vector<LineRun> newLines;
866 // Estimate the number of lines.
867 Length linesCapacity = std::max( 1u, layoutParameters.estimatedNumberOfLines );
868 Length numberOfLines = 0u;
870 if( updateCurrentBuffer )
872 newGlyphPositions.Resize( layoutParameters.numberOfGlyphs );
873 glyphPositionsBuffer = newGlyphPositions.Begin();
875 newLines.Resize( linesCapacity );
876 linesBuffer = newLines.Begin();
880 glyphPositionsBuffer = glyphPositions.Begin();
882 lines.Resize( linesCapacity );
883 linesBuffer = lines.Begin();
886 float penY = CalculateLineOffset( lines,
887 layoutParameters.startLineIndex );
889 for( GlyphIndex index = layoutParameters.startGlyphIndex; index < lastGlyphPlusOne; )
891 CharacterDirection currentParagraphDirection = paragraphDirection;
893 // Get the layout for the line.
895 layout.glyphIndex = index;
896 GetLineLayoutForBox( layoutParameters,
901 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " glyph index %d\n", layout.glyphIndex );
902 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " character index %d\n", layout.characterIndex );
903 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " number of glyphs %d\n", layout.numberOfGlyphs );
904 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " number of characters %d\n", layout.numberOfCharacters );
905 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " length %f\n", layout.length );
907 if( 0u == layout.numberOfGlyphs )
909 // The width is too small and no characters are laid-out.
910 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--LayoutText width too small!\n\n" );
912 lines.Resize( numberOfLines );
916 // Set the line position. Discard if ellipsis is enabled and the position exceeds the boundaries
918 penY += layout.ascender;
920 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " pen y %f\n", penY );
922 bool ellipsis = false;
923 if( elideTextEnabled )
925 // Does the ellipsis of the last line.
926 ellipsis = EllipsisLine( layoutParameters,
930 glyphPositionsBuffer,
933 currentParagraphDirection,
934 isAutoScrollEnabled );
939 // No more lines to layout.
944 // Whether the last line has been laid-out.
945 const bool isLastLine = index + layout.numberOfGlyphs == layoutParameters.totalNumberOfGlyphs;
947 if( numberOfLines == linesCapacity )
949 // Reserve more space for the next lines.
950 linesBuffer = ResizeLinesBuffer( lines,
953 updateCurrentBuffer );
956 // Updates the current text's layout with the line's layout.
957 UpdateTextLayout( layoutParameters,
965 const GlyphIndex nextIndex = index + layout.numberOfGlyphs;
967 if( ( nextIndex == layoutParameters.totalNumberOfGlyphs ) &&
968 layoutParameters.isLastNewParagraph &&
969 ( mLayout == MULTI_LINE_BOX ) )
971 // The last character of the text is a new paragraph character.
972 // An extra line with no characters is added to increase the text's height
973 // in order to place the cursor.
975 if( numberOfLines == linesCapacity )
977 // Reserve more space for the next lines.
978 linesBuffer = ResizeLinesBuffer( lines,
981 updateCurrentBuffer );
984 UpdateTextLayout( layoutParameters,
985 layout.characterIndex + layout.numberOfCharacters,
986 index + layout.numberOfGlyphs,
990 } // whether to add a last line.
992 // Sets the positions of the glyphs.
993 SetGlyphPositions( layoutParameters.glyphsBuffer + index,
994 layout.numberOfGlyphs,
995 layoutParameters.outlineWidth,
996 glyphPositionsBuffer + index - layoutParameters.startGlyphIndex );
998 // Updates the vertical pen's position.
999 penY += -layout.descender + layout.lineSpacing + mDefaultLineSpacing;
1001 // Increase the glyph index.
1004 } // end for() traversing glyphs.
1006 if( updateCurrentBuffer )
1008 glyphPositions.Insert( glyphPositions.Begin() + layoutParameters.startGlyphIndex,
1009 newGlyphPositions.Begin(),
1010 newGlyphPositions.End() );
1011 glyphPositions.Resize( layoutParameters.totalNumberOfGlyphs );
1013 newLines.Resize( numberOfLines );
1015 // Current text's layout size adds only the newly laid-out lines.
1016 // Updates the layout size with the previously laid-out lines.
1017 UpdateLayoutSize( lines,
1020 if( 0u != newLines.Count() )
1022 const LineRun& lastLine = *( newLines.End() - 1u );
1024 const Length characterOffset = lastLine.characterRun.characterIndex + lastLine.characterRun.numberOfCharacters;
1025 const Length glyphOffset = lastLine.glyphRun.glyphIndex + lastLine.glyphRun.numberOfGlyphs;
1027 // Update the indices of the runs before the new laid-out lines are inserted.
1028 UpdateLineIndexOffsets( layoutParameters,
1033 // Insert the lines.
1034 lines.Insert( lines.Begin() + layoutParameters.startLineIndex,
1041 lines.Resize( numberOfLines );
1044 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--LayoutText\n\n" );
1049 void ReLayoutRightToLeftLines( const Parameters& layoutParameters,
1050 CharacterIndex startIndex,
1051 Length numberOfCharacters,
1052 Vector<Vector2>& glyphPositions )
1054 const CharacterIndex lastCharacterIndex = startIndex + numberOfCharacters;
1056 // Traverses the paragraphs with right to left characters.
1057 for( LineIndex lineIndex = 0u; lineIndex < layoutParameters.numberOfBidirectionalInfoRuns; ++lineIndex )
1059 const BidirectionalLineInfoRun& bidiLine = *( layoutParameters.lineBidirectionalInfoRunsBuffer + lineIndex );
1061 if( startIndex >= bidiLine.characterRun.characterIndex + bidiLine.characterRun.numberOfCharacters )
1063 // Do not reorder the line if it has been already reordered.
1067 if( bidiLine.characterRun.characterIndex >= lastCharacterIndex )
1069 // Do not reorder the lines after the last requested character.
1073 const CharacterIndex characterVisualIndex = bidiLine.characterRun.characterIndex + *bidiLine.visualToLogicalMap;
1074 const GlyphInfo& glyph = *( layoutParameters.glyphsBuffer + *( layoutParameters.charactersToGlyphsBuffer + characterVisualIndex ) );
1076 float penX = ( 0.f > glyph.xBearing ) ? -glyph.xBearing - layoutParameters.outlineWidth : -layoutParameters.outlineWidth;
1078 Vector2* glyphPositionsBuffer = glyphPositions.Begin();
1080 // Traverses the characters of the right to left paragraph.
1081 for( CharacterIndex characterLogicalIndex = 0u;
1082 characterLogicalIndex < bidiLine.characterRun.numberOfCharacters;
1083 ++characterLogicalIndex )
1085 // Convert the character in the logical order into the character in the visual order.
1086 const CharacterIndex characterVisualIndex = bidiLine.characterRun.characterIndex + *( bidiLine.visualToLogicalMap + characterLogicalIndex );
1088 // Get the number of glyphs of the character.
1089 const Length numberOfGlyphs = *( layoutParameters.glyphsPerCharacterBuffer + characterVisualIndex );
1091 for( GlyphIndex index = 0u; index < numberOfGlyphs; ++index )
1093 // Convert the character in the visual order into the glyph in the visual order.
1094 const GlyphIndex glyphIndex = *( layoutParameters.charactersToGlyphsBuffer + characterVisualIndex ) + index;
1096 DALI_ASSERT_DEBUG( 0u <= glyphIndex && glyphIndex < layoutParameters.totalNumberOfGlyphs );
1098 const GlyphInfo& glyph = *( layoutParameters.glyphsBuffer + glyphIndex );
1099 Vector2& position = *( glyphPositionsBuffer + glyphIndex );
1101 position.x = penX + glyph.xBearing;
1102 penX += glyph.advance;
1108 void Align( const Size& size,
1109 CharacterIndex startIndex,
1110 Length numberOfCharacters,
1111 Text::HorizontalAlignment::Type horizontalAlignment,
1112 Vector<LineRun>& lines,
1113 float& alignmentOffset,
1114 Dali::LayoutDirection::Type layoutDirection,
1115 bool matchSystemLanguageDirection )
1117 const CharacterIndex lastCharacterPlusOne = startIndex + numberOfCharacters;
1119 alignmentOffset = MAX_FLOAT;
1120 // Traverse all lines and align the glyphs.
1121 for( Vector<LineRun>::Iterator it = lines.Begin(), endIt = lines.End();
1125 LineRun& line = *it;
1127 if( line.characterRun.characterIndex < startIndex )
1129 // Do not align lines which have already been aligned.
1133 if( line.characterRun.characterIndex >= lastCharacterPlusOne )
1135 // Do not align lines beyond the last laid-out character.
1139 // Calculate the line's alignment offset accordingly with the align option,
1140 // the box width, line length, and the paragraph's direction.
1141 CalculateHorizontalAlignment( size.width,
1142 horizontalAlignment,
1145 matchSystemLanguageDirection );
1147 // Updates the alignment offset.
1148 alignmentOffset = std::min( alignmentOffset, line.alignmentOffset );
1152 void CalculateHorizontalAlignment( float boxWidth,
1153 HorizontalAlignment::Type horizontalAlignment,
1155 Dali::LayoutDirection::Type layoutDirection,
1156 bool matchSystemLanguageDirection )
1158 line.alignmentOffset = 0.f;
1159 const bool isLineRTL = RTL == line.direction;
1160 // Whether to swap the alignment.
1161 // 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.
1162 bool isLayoutRTL = isLineRTL;
1163 float lineLength = line.width;
1165 // match align for system language direction
1166 if( matchSystemLanguageDirection )
1168 // Swap the alignment type if the line is right to left.
1169 isLayoutRTL = layoutDirection == LayoutDirection::RIGHT_TO_LEFT;
1171 // Calculate the horizontal line offset.
1172 switch( horizontalAlignment )
1174 case HorizontalAlignment::BEGIN:
1180 lineLength += line.extraLength;
1183 line.alignmentOffset = boxWidth - lineLength;
1187 line.alignmentOffset = 0.f;
1191 // 'Remove' the white spaces at the end of the line (which are at the beginning in visual order)
1192 line.alignmentOffset -= line.extraLength;
1197 case HorizontalAlignment::CENTER:
1199 line.alignmentOffset = 0.5f * ( boxWidth - lineLength );
1203 line.alignmentOffset -= line.extraLength;
1206 line.alignmentOffset = floorf( line.alignmentOffset ); // try to avoid pixel alignment.
1209 case HorizontalAlignment::END:
1213 line.alignmentOffset = 0.f;
1217 // 'Remove' the white spaces at the end of the line (which are at the beginning in visual order)
1218 line.alignmentOffset -= line.extraLength;
1225 lineLength += line.extraLength;
1228 line.alignmentOffset = boxWidth - lineLength;
1235 void Initialize( LineRun& line )
1237 line.glyphRun.glyphIndex = 0u;
1238 line.glyphRun.numberOfGlyphs = 0u;
1239 line.characterRun.characterIndex = 0u;
1240 line.characterRun.numberOfCharacters = 0u;
1242 line.ascender = 0.f;
1243 line.descender = 0.f;
1244 line.extraLength = 0.f;
1245 line.alignmentOffset = 0.f;
1246 line.direction = !RTL;
1247 line.ellipsis = false;
1248 line.lineSpacing = mDefaultLineSpacing;
1253 float mDefaultLineSpacing;
1254 float mPreviousCharacterExtraWidth;
1256 IntrusivePtr<Metrics> mMetrics;
1262 mImpl = new Engine::Impl();
1270 void Engine::SetMetrics( MetricsPtr& metrics )
1272 mImpl->mMetrics = metrics;
1275 void Engine::SetLayout( Type layout )
1277 mImpl->mLayout = layout;
1280 Engine::Type Engine::GetLayout() const
1282 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "GetLayout[%d]\n", mImpl->mLayout);
1283 return mImpl->mLayout;
1286 void Engine::SetCursorWidth( int width )
1288 mImpl->mCursorWidth = static_cast<float>( width );
1291 int Engine::GetCursorWidth() const
1293 return static_cast<int>( mImpl->mCursorWidth );
1296 bool Engine::LayoutText( const Parameters& layoutParameters,
1297 Vector<Vector2>& glyphPositions,
1298 Vector<LineRun>& lines,
1300 bool elideTextEnabled,
1301 bool& isAutoScrollEnabled )
1303 return mImpl->LayoutText( layoutParameters,
1308 isAutoScrollEnabled );
1311 void Engine::ReLayoutRightToLeftLines( const Parameters& layoutParameters,
1312 CharacterIndex startIndex,
1313 Length numberOfCharacters,
1314 Vector<Vector2>& glyphPositions )
1316 mImpl->ReLayoutRightToLeftLines( layoutParameters,
1322 void Engine::Align( const Size& size,
1323 CharacterIndex startIndex,
1324 Length numberOfCharacters,
1325 Text::HorizontalAlignment::Type horizontalAlignment,
1326 Vector<LineRun>& lines,
1327 float& alignmentOffset,
1328 Dali::LayoutDirection::Type layoutDirection,
1329 bool matchSystemLanguageDirection )
1334 horizontalAlignment,
1338 matchSystemLanguageDirection );
1341 void Engine::SetDefaultLineSpacing( float lineSpacing )
1343 mImpl->mDefaultLineSpacing = lineSpacing;
1346 float Engine::GetDefaultLineSpacing() const
1348 return mImpl->mDefaultLineSpacing;
1351 } // namespace Layout
1355 } // namespace Toolkit