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.
559 * return Whether the line is ellipsized.
561 bool EllipsisLine( const Parameters& layoutParameters,
562 const LineLayout& layout,
564 LineRun* linesBuffer,
565 Vector2* glyphPositionsBuffer,
566 Length& numberOfLines,
568 CharacterDirection currentParagraphDirection )
570 const bool ellipsis = ( ( penY - layout.descender > layoutParameters.boundingBox.height ) ||
571 ( ( mLayout == SINGLE_LINE_BOX ) &&
572 ( layout.extraBearing + layout.length + layout.extraWidth > layoutParameters.boundingBox.width ) ) );
576 // Do not layout more lines if ellipsis is enabled.
578 // The last line needs to be completely filled with characters.
579 // Part of a word may be used.
581 LineRun* lineRun = NULL;
582 LineLayout ellipsisLayout;
583 if( 0u != numberOfLines )
585 // Get the last line and layout it again with the 'completelyFill' flag to true.
586 lineRun = linesBuffer + ( numberOfLines - 1u );
588 penY -= layout.ascender - lineRun->descender + lineRun->lineSpacing;
590 ellipsisLayout.glyphIndex = lineRun->glyphRun.glyphIndex;
594 // At least there is space reserved for one line.
595 lineRun = linesBuffer;
597 lineRun->glyphRun.glyphIndex = 0u;
598 ellipsisLayout.glyphIndex = 0u;
603 GetLineLayoutForBox( layoutParameters,
605 currentParagraphDirection,
608 lineRun->glyphRun.numberOfGlyphs = ellipsisLayout.numberOfGlyphs;
609 lineRun->characterRun.characterIndex = ellipsisLayout.characterIndex;
610 lineRun->characterRun.numberOfCharacters = ellipsisLayout.numberOfCharacters;
611 lineRun->width = ellipsisLayout.length;
612 lineRun->extraLength = ( ellipsisLayout.wsLengthEndOfLine > 0.f ) ? ellipsisLayout.wsLengthEndOfLine - ellipsisLayout.extraWidth : 0.f;
613 lineRun->ascender = ellipsisLayout.ascender;
614 lineRun->descender = ellipsisLayout.descender;
615 lineRun->direction = !RTL;
616 lineRun->ellipsis = true;
618 layoutSize.width = layoutParameters.boundingBox.width;
619 if( layoutSize.height < Math::MACHINE_EPSILON_1000 )
621 layoutSize.height += ( lineRun->ascender + -lineRun->descender ) + lineRun->lineSpacing;
624 SetGlyphPositions( layoutParameters.glyphsBuffer + lineRun->glyphRun.glyphIndex,
625 ellipsisLayout.numberOfGlyphs,
626 layoutParameters.outlineWidth,
627 glyphPositionsBuffer + lineRun->glyphRun.glyphIndex - layoutParameters.startGlyphIndex );
634 * @brief Updates the text layout with a new laid-out line.
636 * @param[in] layoutParameters The parameters needed to layout the text.
637 * @param[in] layout The line layout.
638 * @param[in,out] layoutSize The text's layout size.
639 * @param[in,out] linesBuffer Pointer to the line's buffer.
640 * @param[in] index Index to the vector of glyphs.
641 * @param[in,out] numberOfLines The number of laid-out lines.
642 * @param[in] isLastLine Whether the laid-out line is the last one.
644 void UpdateTextLayout( const Parameters& layoutParameters,
645 const LineLayout& layout,
647 LineRun* linesBuffer,
649 Length& numberOfLines,
652 LineRun& lineRun = *( linesBuffer + numberOfLines );
655 lineRun.glyphRun.glyphIndex = index;
656 lineRun.glyphRun.numberOfGlyphs = layout.numberOfGlyphs;
657 lineRun.characterRun.characterIndex = layout.characterIndex;
658 lineRun.characterRun.numberOfCharacters = layout.numberOfCharacters;
659 lineRun.lineSpacing = mDefaultLineSpacing;
661 if( isLastLine && !layoutParameters.isLastNewParagraph )
663 const float width = layout.extraBearing + layout.length + layout.extraWidth + layout.wsLengthEndOfLine;
664 if( MULTI_LINE_BOX == mLayout )
666 lineRun.width = ( width > layoutParameters.boundingBox.width ) ? layoutParameters.boundingBox.width : width;
670 lineRun.width = width;
673 lineRun.extraLength = 0.f;
677 lineRun.width = layout.extraBearing + layout.length + layout.extraWidth;
678 lineRun.extraLength = ( layout.wsLengthEndOfLine > 0.f ) ? layout.wsLengthEndOfLine - layout.extraWidth : 0.f;
680 lineRun.ascender = layout.ascender;
681 lineRun.descender = layout.descender;
682 lineRun.direction = !RTL;
683 lineRun.ellipsis = false;
685 // Update the actual size.
686 if( lineRun.width > layoutSize.width )
688 layoutSize.width = lineRun.width;
691 layoutSize.height += ( lineRun.ascender + -lineRun.descender ) + lineRun.lineSpacing;
695 * @brief Updates the text layout with the last laid-out line.
697 * @param[in] layoutParameters The parameters needed to layout the text.
698 * @param[in] characterIndex The character index of the line.
699 * @param[in] glyphIndex The glyph index of the line.
700 * @param[in,out] layoutSize The text's layout size.
701 * @param[in,out] linesBuffer Pointer to the line's buffer.
702 * @param[in,out] numberOfLines The number of laid-out lines.
704 void UpdateTextLayout( const Parameters& layoutParameters,
705 CharacterIndex characterIndex,
706 GlyphIndex glyphIndex,
708 LineRun* linesBuffer,
709 Length& numberOfLines )
711 // Need to add a new line with no characters but with height to increase the layoutSize.height
712 const GlyphInfo& glyphInfo = *( layoutParameters.glyphsBuffer + layoutParameters.totalNumberOfGlyphs - 1u );
714 Text::FontMetrics fontMetrics;
715 mMetrics->GetFontMetrics( glyphInfo.fontId, fontMetrics );
717 LineRun& lineRun = *( linesBuffer + numberOfLines );
720 lineRun.glyphRun.glyphIndex = glyphIndex;
721 lineRun.glyphRun.numberOfGlyphs = 0u;
722 lineRun.characterRun.characterIndex = characterIndex;
723 lineRun.characterRun.numberOfCharacters = 0u;
725 lineRun.ascender = fontMetrics.ascender;
726 lineRun.descender = fontMetrics.descender;
727 lineRun.extraLength = 0.f;
728 lineRun.alignmentOffset = 0.f;
729 lineRun.direction = !RTL;
730 lineRun.ellipsis = false;
731 lineRun.lineSpacing = mDefaultLineSpacing;
733 layoutSize.height += ( lineRun.ascender + -lineRun.descender ) + lineRun.lineSpacing;
737 * @brief Updates the text's layout size adding the size of the previously laid-out lines.
739 * @param[in] lines The vector of lines (before the new laid-out lines are inserted).
740 * @param[in,out] layoutSize The text's layout size.
742 void UpdateLayoutSize( const Vector<LineRun>& lines,
745 for( Vector<LineRun>::ConstIterator it = lines.Begin(),
750 const LineRun& line = *it;
752 if( line.width > layoutSize.width )
754 layoutSize.width = line.width;
757 layoutSize.height += ( line.ascender + -line.descender ) + line.lineSpacing;
762 * @brief Updates the indices of the character and glyph runs of the lines before the new lines are inserted.
764 * @param[in] layoutParameters The parameters needed to layout the text.
765 * @param[in,out] lines The vector of lines (before the new laid-out lines are inserted).
766 * @param[in] characterOffset The offset to be added to the runs of characters.
767 * @param[in] glyphOffset The offset to be added to the runs of glyphs.
769 void UpdateLineIndexOffsets( const Parameters& layoutParameters,
770 Vector<LineRun>& lines,
771 Length characterOffset,
774 // Update the glyph and character runs.
775 for( Vector<LineRun>::Iterator it = lines.Begin() + layoutParameters.startLineIndex,
782 line.glyphRun.glyphIndex = glyphOffset;
783 line.characterRun.characterIndex = characterOffset;
785 glyphOffset += line.glyphRun.numberOfGlyphs;
786 characterOffset += line.characterRun.numberOfCharacters;
790 bool LayoutText( const Parameters& layoutParameters,
791 Vector<Vector2>& glyphPositions,
792 Vector<LineRun>& lines,
794 bool elideTextEnabled )
796 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "-->LayoutText\n" );
797 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " box size %f, %f\n", layoutParameters.boundingBox.width, layoutParameters.boundingBox.height );
799 if( 0u == layoutParameters.numberOfGlyphs )
801 // Add an extra line if the last character is a new paragraph character and the last line doesn't have zero characters.
802 if( layoutParameters.isLastNewParagraph )
804 Length numberOfLines = lines.Count();
805 if( 0u != numberOfLines )
807 const LineRun& lastLine = *( lines.End() - 1u );
809 if( 0u != lastLine.characterRun.numberOfCharacters )
811 // Need to add a new line with no characters but with height to increase the layoutSize.height
813 Initialize( newLine );
814 lines.PushBack( newLine );
816 UpdateTextLayout( layoutParameters,
817 lastLine.characterRun.characterIndex + lastLine.characterRun.numberOfCharacters,
818 lastLine.glyphRun.glyphIndex + lastLine.glyphRun.numberOfGlyphs,
826 // Calculates the layout size.
827 UpdateLayoutSize( lines,
830 // Nothing else do if there are no glyphs to layout.
834 const GlyphIndex lastGlyphPlusOne = layoutParameters.startGlyphIndex + layoutParameters.numberOfGlyphs;
836 // In a previous layout, an extra line with no characters may have been added if the text ended with a new paragraph character.
837 // This extra line needs to be removed.
838 if( 0u != lines.Count() )
840 Vector<LineRun>::Iterator lastLine = lines.End() - 1u;
842 if( ( 0u == lastLine->characterRun.numberOfCharacters ) &&
843 ( lastGlyphPlusOne == layoutParameters.totalNumberOfGlyphs ) )
845 lines.Remove( lastLine );
849 // Set the first paragraph's direction.
850 CharacterDirection paragraphDirection = ( NULL != layoutParameters.characterDirectionBuffer ) ? *layoutParameters.characterDirectionBuffer : !RTL;
852 // Whether the layout is being updated or set from scratch.
853 const bool updateCurrentBuffer = layoutParameters.numberOfGlyphs < layoutParameters.totalNumberOfGlyphs;
855 Vector2* glyphPositionsBuffer = NULL;
856 Vector<Vector2> newGlyphPositions;
858 LineRun* linesBuffer = NULL;
859 Vector<LineRun> newLines;
861 // Estimate the number of lines.
862 Length linesCapacity = std::max( 1u, layoutParameters.estimatedNumberOfLines );
863 Length numberOfLines = 0u;
865 if( updateCurrentBuffer )
867 newGlyphPositions.Resize( layoutParameters.numberOfGlyphs );
868 glyphPositionsBuffer = newGlyphPositions.Begin();
870 newLines.Resize( linesCapacity );
871 linesBuffer = newLines.Begin();
875 glyphPositionsBuffer = glyphPositions.Begin();
877 lines.Resize( linesCapacity );
878 linesBuffer = lines.Begin();
881 float penY = CalculateLineOffset( lines,
882 layoutParameters.startLineIndex );
884 for( GlyphIndex index = layoutParameters.startGlyphIndex; index < lastGlyphPlusOne; )
886 CharacterDirection currentParagraphDirection = paragraphDirection;
888 // Get the layout for the line.
890 layout.glyphIndex = index;
891 GetLineLayoutForBox( layoutParameters,
896 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " glyph index %d\n", layout.glyphIndex );
897 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " character index %d\n", layout.characterIndex );
898 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " number of glyphs %d\n", layout.numberOfGlyphs );
899 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " number of characters %d\n", layout.numberOfCharacters );
900 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " length %f\n", layout.length );
902 if( 0u == layout.numberOfGlyphs )
904 // The width is too small and no characters are laid-out.
905 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--LayoutText width too small!\n\n" );
907 lines.Resize( numberOfLines );
911 // Set the line position. Discard if ellipsis is enabled and the position exceeds the boundaries
913 penY += layout.ascender;
915 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " pen y %f\n", penY );
917 bool ellipsis = false;
918 if( elideTextEnabled )
920 // Does the ellipsis of the last line.
921 ellipsis = EllipsisLine( layoutParameters,
925 glyphPositionsBuffer,
928 currentParagraphDirection );
933 // No more lines to layout.
938 // Whether the last line has been laid-out.
939 const bool isLastLine = index + layout.numberOfGlyphs == layoutParameters.totalNumberOfGlyphs;
941 if( numberOfLines == linesCapacity )
943 // Reserve more space for the next lines.
944 linesBuffer = ResizeLinesBuffer( lines,
947 updateCurrentBuffer );
950 // Updates the current text's layout with the line's layout.
951 UpdateTextLayout( layoutParameters,
959 const GlyphIndex nextIndex = index + layout.numberOfGlyphs;
961 if( ( nextIndex == layoutParameters.totalNumberOfGlyphs ) &&
962 layoutParameters.isLastNewParagraph &&
963 ( mLayout == MULTI_LINE_BOX ) )
965 // The last character of the text is a new paragraph character.
966 // An extra line with no characters is added to increase the text's height
967 // in order to place the cursor.
969 if( numberOfLines == linesCapacity )
971 // Reserve more space for the next lines.
972 linesBuffer = ResizeLinesBuffer( lines,
975 updateCurrentBuffer );
978 UpdateTextLayout( layoutParameters,
979 layout.characterIndex + layout.numberOfCharacters,
980 index + layout.numberOfGlyphs,
984 } // whether to add a last line.
986 // Sets the positions of the glyphs.
987 SetGlyphPositions( layoutParameters.glyphsBuffer + index,
988 layout.numberOfGlyphs,
989 layoutParameters.outlineWidth,
990 glyphPositionsBuffer + index - layoutParameters.startGlyphIndex );
992 // Updates the vertical pen's position.
993 penY += -layout.descender + layout.lineSpacing + mDefaultLineSpacing;
995 // Increase the glyph index.
998 } // end for() traversing glyphs.
1000 if( updateCurrentBuffer )
1002 glyphPositions.Insert( glyphPositions.Begin() + layoutParameters.startGlyphIndex,
1003 newGlyphPositions.Begin(),
1004 newGlyphPositions.End() );
1005 glyphPositions.Resize( layoutParameters.totalNumberOfGlyphs );
1007 newLines.Resize( numberOfLines );
1009 // Current text's layout size adds only the newly laid-out lines.
1010 // Updates the layout size with the previously laid-out lines.
1011 UpdateLayoutSize( lines,
1014 if( 0u != newLines.Count() )
1016 const LineRun& lastLine = *( newLines.End() - 1u );
1018 const Length characterOffset = lastLine.characterRun.characterIndex + lastLine.characterRun.numberOfCharacters;
1019 const Length glyphOffset = lastLine.glyphRun.glyphIndex + lastLine.glyphRun.numberOfGlyphs;
1021 // Update the indices of the runs before the new laid-out lines are inserted.
1022 UpdateLineIndexOffsets( layoutParameters,
1027 // Insert the lines.
1028 lines.Insert( lines.Begin() + layoutParameters.startLineIndex,
1035 lines.Resize( numberOfLines );
1038 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--LayoutText\n\n" );
1043 void ReLayoutRightToLeftLines( const Parameters& layoutParameters,
1044 CharacterIndex startIndex,
1045 Length numberOfCharacters,
1046 Vector<Vector2>& glyphPositions )
1048 const CharacterIndex lastCharacterIndex = startIndex + numberOfCharacters;
1050 // Traverses the paragraphs with right to left characters.
1051 for( LineIndex lineIndex = 0u; lineIndex < layoutParameters.numberOfBidirectionalInfoRuns; ++lineIndex )
1053 const BidirectionalLineInfoRun& bidiLine = *( layoutParameters.lineBidirectionalInfoRunsBuffer + lineIndex );
1055 if( startIndex >= bidiLine.characterRun.characterIndex + bidiLine.characterRun.numberOfCharacters )
1057 // Do not reorder the line if it has been already reordered.
1061 if( bidiLine.characterRun.characterIndex >= lastCharacterIndex )
1063 // Do not reorder the lines after the last requested character.
1067 const CharacterIndex characterVisualIndex = bidiLine.characterRun.characterIndex + *bidiLine.visualToLogicalMap;
1068 const GlyphInfo& glyph = *( layoutParameters.glyphsBuffer + *( layoutParameters.charactersToGlyphsBuffer + characterVisualIndex ) );
1070 float penX = ( 0.f > glyph.xBearing ) ? -glyph.xBearing - layoutParameters.outlineWidth : -layoutParameters.outlineWidth;
1072 Vector2* glyphPositionsBuffer = glyphPositions.Begin();
1074 // Traverses the characters of the right to left paragraph.
1075 for( CharacterIndex characterLogicalIndex = 0u;
1076 characterLogicalIndex < bidiLine.characterRun.numberOfCharacters;
1077 ++characterLogicalIndex )
1079 // Convert the character in the logical order into the character in the visual order.
1080 const CharacterIndex characterVisualIndex = bidiLine.characterRun.characterIndex + *( bidiLine.visualToLogicalMap + characterLogicalIndex );
1082 // Get the number of glyphs of the character.
1083 const Length numberOfGlyphs = *( layoutParameters.glyphsPerCharacterBuffer + characterVisualIndex );
1085 for( GlyphIndex index = 0u; index < numberOfGlyphs; ++index )
1087 // Convert the character in the visual order into the glyph in the visual order.
1088 const GlyphIndex glyphIndex = *( layoutParameters.charactersToGlyphsBuffer + characterVisualIndex ) + index;
1090 DALI_ASSERT_DEBUG( 0u <= glyphIndex && glyphIndex < layoutParameters.totalNumberOfGlyphs );
1092 const GlyphInfo& glyph = *( layoutParameters.glyphsBuffer + glyphIndex );
1093 Vector2& position = *( glyphPositionsBuffer + glyphIndex );
1095 position.x = penX + glyph.xBearing;
1096 penX += glyph.advance;
1102 void Align( const Size& size,
1103 CharacterIndex startIndex,
1104 Length numberOfCharacters,
1105 Text::HorizontalAlignment::Type horizontalAlignment,
1106 Vector<LineRun>& lines,
1107 float& alignmentOffset,
1108 Dali::LayoutDirection::Type layoutDirection,
1109 bool matchSystemLanguageDirection )
1111 const CharacterIndex lastCharacterPlusOne = startIndex + numberOfCharacters;
1113 alignmentOffset = MAX_FLOAT;
1114 // Traverse all lines and align the glyphs.
1115 for( Vector<LineRun>::Iterator it = lines.Begin(), endIt = lines.End();
1119 LineRun& line = *it;
1121 if( line.characterRun.characterIndex < startIndex )
1123 // Do not align lines which have already been aligned.
1127 if( line.characterRun.characterIndex >= lastCharacterPlusOne )
1129 // Do not align lines beyond the last laid-out character.
1133 // Calculate the line's alignment offset accordingly with the align option,
1134 // the box width, line length, and the paragraph's direction.
1135 CalculateHorizontalAlignment( size.width,
1136 horizontalAlignment,
1139 matchSystemLanguageDirection );
1141 // Updates the alignment offset.
1142 alignmentOffset = std::min( alignmentOffset, line.alignmentOffset );
1146 void CalculateHorizontalAlignment( float boxWidth,
1147 HorizontalAlignment::Type horizontalAlignment,
1149 Dali::LayoutDirection::Type layoutDirection,
1150 bool matchSystemLanguageDirection )
1152 line.alignmentOffset = 0.f;
1153 const bool isLineRTL = RTL == line.direction;
1154 // Whether to swap the alignment.
1155 // 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.
1156 bool isLayoutRTL = isLineRTL;
1157 float lineLength = line.width;
1159 // match align for system language direction
1160 if( matchSystemLanguageDirection )
1162 // Swap the alignment type if the line is right to left.
1163 isLayoutRTL = layoutDirection == LayoutDirection::RIGHT_TO_LEFT;
1165 // Calculate the horizontal line offset.
1166 switch( horizontalAlignment )
1168 case HorizontalAlignment::BEGIN:
1174 lineLength += line.extraLength;
1177 line.alignmentOffset = boxWidth - lineLength;
1181 line.alignmentOffset = 0.f;
1185 // 'Remove' the white spaces at the end of the line (which are at the beginning in visual order)
1186 line.alignmentOffset -= line.extraLength;
1191 case HorizontalAlignment::CENTER:
1193 line.alignmentOffset = 0.5f * ( boxWidth - lineLength );
1197 line.alignmentOffset -= line.extraLength;
1200 line.alignmentOffset = floorf( line.alignmentOffset ); // try to avoid pixel alignment.
1203 case HorizontalAlignment::END:
1207 line.alignmentOffset = 0.f;
1211 // 'Remove' the white spaces at the end of the line (which are at the beginning in visual order)
1212 line.alignmentOffset -= line.extraLength;
1219 lineLength += line.extraLength;
1222 line.alignmentOffset = boxWidth - lineLength;
1229 void Initialize( LineRun& line )
1231 line.glyphRun.glyphIndex = 0u;
1232 line.glyphRun.numberOfGlyphs = 0u;
1233 line.characterRun.characterIndex = 0u;
1234 line.characterRun.numberOfCharacters = 0u;
1236 line.ascender = 0.f;
1237 line.descender = 0.f;
1238 line.extraLength = 0.f;
1239 line.alignmentOffset = 0.f;
1240 line.direction = !RTL;
1241 line.ellipsis = false;
1242 line.lineSpacing = mDefaultLineSpacing;
1247 float mDefaultLineSpacing;
1248 float mPreviousCharacterExtraWidth;
1250 IntrusivePtr<Metrics> mMetrics;
1256 mImpl = new Engine::Impl();
1264 void Engine::SetMetrics( MetricsPtr& metrics )
1266 mImpl->mMetrics = metrics;
1269 void Engine::SetLayout( Type layout )
1271 mImpl->mLayout = layout;
1274 Engine::Type Engine::GetLayout() const
1276 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "GetLayout[%d]\n", mImpl->mLayout);
1277 return mImpl->mLayout;
1280 void Engine::SetCursorWidth( int width )
1282 mImpl->mCursorWidth = static_cast<float>( width );
1285 int Engine::GetCursorWidth() const
1287 return static_cast<int>( mImpl->mCursorWidth );
1290 bool Engine::LayoutText( const Parameters& layoutParameters,
1291 Vector<Vector2>& glyphPositions,
1292 Vector<LineRun>& lines,
1294 bool elideTextEnabled )
1296 return mImpl->LayoutText( layoutParameters,
1303 void Engine::ReLayoutRightToLeftLines( const Parameters& layoutParameters,
1304 CharacterIndex startIndex,
1305 Length numberOfCharacters,
1306 Vector<Vector2>& glyphPositions )
1308 mImpl->ReLayoutRightToLeftLines( layoutParameters,
1314 void Engine::Align( const Size& size,
1315 CharacterIndex startIndex,
1316 Length numberOfCharacters,
1317 Text::HorizontalAlignment::Type horizontalAlignment,
1318 Vector<LineRun>& lines,
1319 float& alignmentOffset,
1320 Dali::LayoutDirection::Type layoutDirection,
1321 bool matchSystemLanguageDirection )
1326 horizontalAlignment,
1330 matchSystemLanguageDirection );
1333 void Engine::SetDefaultLineSpacing( float lineSpacing )
1335 mImpl->mDefaultLineSpacing = lineSpacing;
1338 float Engine::GetDefaultLineSpacing() const
1340 return mImpl->mDefaultLineSpacing;
1343 } // namespace Layout
1347 } // namespace Toolkit