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] glyphMetrics The metrics of the new font.
121 * @param[in,out] lineLayout The line layout.
123 void UpdateLineHeight( const GlyphMetrics& glyphMetrics, LineLayout& lineLayout )
125 Text::FontMetrics fontMetrics;
126 if( 0u != glyphMetrics.fontId )
128 mMetrics->GetFontMetrics( glyphMetrics.fontId, fontMetrics );
132 fontMetrics.ascender = glyphMetrics.fontHeight;
133 fontMetrics.descender = 0.f;
134 fontMetrics.height = fontMetrics.ascender;
135 fontMetrics.underlinePosition = 0.f;
136 fontMetrics.underlineThickness = 1.f;
139 // Sets the maximum ascender.
140 lineLayout.ascender = std::max( lineLayout.ascender, fontMetrics.ascender );
142 // Sets the minimum descender.
143 lineLayout.descender = std::min( lineLayout.descender, fontMetrics.descender );
145 // set the line spacing
146 lineLayout.lineSpacing = mDefaultLineSpacing;
150 * @brief Merges a temporary line layout into the line layout.
152 * @param[in,out] lineLayout The line layout.
153 * @param[in] tmpLineLayout A temporary line layout.
155 void MergeLineLayout( LineLayout& lineLayout,
156 const LineLayout& tmpLineLayout )
158 lineLayout.numberOfCharacters += tmpLineLayout.numberOfCharacters;
159 lineLayout.numberOfGlyphs += tmpLineLayout.numberOfGlyphs;
160 lineLayout.length += tmpLineLayout.length;
162 if( 0.f < tmpLineLayout.length )
164 lineLayout.length += lineLayout.wsLengthEndOfLine;
166 lineLayout.wsLengthEndOfLine = tmpLineLayout.wsLengthEndOfLine;
170 lineLayout.wsLengthEndOfLine += tmpLineLayout.wsLengthEndOfLine;
173 // Sets the maximum ascender.
174 lineLayout.ascender = std::max( lineLayout.ascender, tmpLineLayout.ascender );
176 // Sets the minimum descender.
177 lineLayout.descender = std::min( lineLayout.descender, tmpLineLayout.descender );
181 * Retrieves the line layout for a given box width.
183 * @note This method lais out text as it were left to right. At this point is not possible to reorder the line
184 * because the number of characters of the line is not known (one of the responsabilities of this method
185 * is calculate that). Due to glyph's 'x' bearing, width and advance, when right to left or mixed right to left
186 * and left to right text is laid-out, it can be small differences in the line length. One solution is to
187 * reorder and re-lay out the text after this method and add or remove one extra glyph if needed. However,
188 * this method calculates which are the first and last glyphs of the line (the ones that causes the
189 * differences). This is a good point to check if there is problems with the text exceeding the boundaries
190 * of the control when there is right to left text.
192 * @param[in] parameters The layout parameters.
193 * @param[out] lineLayout The line layout.
194 * @param[in,out] paragraphDirection in: the current paragraph's direction, out: the next paragraph's direction. Is set after a must break.
195 * @param[in] completelyFill Whether to completely fill the line ( even if the last word exceeds the boundaries ).
197 void GetLineLayoutForBox( const Parameters& parameters,
198 LineLayout& lineLayout,
199 CharacterDirection& paragraphDirection,
200 bool completelyFill )
202 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "-->GetLineLayoutForBox\n" );
203 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " initial glyph index : %d\n", lineLayout.glyphIndex );
204 // Stores temporary line layout which has not been added to the final line layout.
205 LineLayout tmpLineLayout;
207 const bool isMultiline = mLayout == MULTI_LINE_BOX;
208 const bool isWordLaidOut = parameters.lineWrapMode == Text::LineWrap::WORD;
210 // The last glyph to be laid-out.
211 const GlyphIndex lastGlyphOfParagraphPlusOne = parameters.startGlyphIndex + parameters.numberOfGlyphs;
213 // If the first glyph has a negative bearing its absolute value needs to be added to the line length.
214 // In the case the line starts with a right to left character, if the width is longer than the advance,
215 // the difference needs to be added to the line length.
217 // Check whether the first glyph comes from a character that is shaped in multiple glyphs.
218 const Length numberOfGLyphsInGroup = GetNumberOfGlyphsOfGroup( lineLayout.glyphIndex,
219 lastGlyphOfParagraphPlusOne,
220 parameters.charactersPerGlyphBuffer );
222 GlyphMetrics glyphMetrics;
223 GetGlyphsMetrics( lineLayout.glyphIndex,
224 numberOfGLyphsInGroup,
226 parameters.glyphsBuffer,
229 // Set the direction of the first character of the line.
230 lineLayout.characterIndex = *( parameters.glyphsToCharactersBuffer + lineLayout.glyphIndex );
231 const CharacterDirection firstCharacterDirection = ( NULL == parameters.characterDirectionBuffer ) ? false : *( parameters.characterDirectionBuffer + lineLayout.characterIndex );
232 CharacterDirection previousCharacterDirection = firstCharacterDirection;
234 const float extraWidth = glyphMetrics.xBearing + glyphMetrics.width - glyphMetrics.advance;
235 float tmpExtraWidth = ( 0.f < extraWidth ) ? extraWidth : 0.f;
237 float tmpExtraBearing = ( 0.f > glyphMetrics.xBearing ) ? -glyphMetrics.xBearing : 0.f;
239 tmpLineLayout.length += mCursorWidth; // Added to give some space to the cursor.
241 // Calculate the line height if there is no characters.
242 FontId lastFontId = glyphMetrics.fontId;
243 UpdateLineHeight( glyphMetrics, tmpLineLayout );
245 bool oneWordLaidOut = false;
247 for( GlyphIndex glyphIndex = lineLayout.glyphIndex;
248 glyphIndex < lastGlyphOfParagraphPlusOne; )
250 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " glyph index : %d\n", glyphIndex );
252 // Check whether this glyph comes from a character that is shaped in multiple glyphs.
253 const Length numberOfGLyphsInGroup = GetNumberOfGlyphsOfGroup( glyphIndex,
254 lastGlyphOfParagraphPlusOne,
255 parameters.charactersPerGlyphBuffer );
257 GlyphMetrics glyphMetrics;
258 GetGlyphsMetrics( glyphIndex,
259 numberOfGLyphsInGroup,
261 parameters.glyphsBuffer,
264 const bool isLastGlyph = glyphIndex + numberOfGLyphsInGroup == parameters.totalNumberOfGlyphs;
266 // Check if the font of the current glyph is the same of the previous one.
267 // If it's different the ascender and descender need to be updated.
268 if( lastFontId != glyphMetrics.fontId )
270 UpdateLineHeight( glyphMetrics, tmpLineLayout );
271 lastFontId = glyphMetrics.fontId;
274 // Get the character indices for the current glyph. The last character index is needed
275 // because there are glyphs formed by more than one character but their break info is
276 // given only for the last character.
277 const Length charactersPerGlyph = *( parameters.charactersPerGlyphBuffer + glyphIndex + numberOfGLyphsInGroup - 1u );
278 const bool hasCharacters = charactersPerGlyph > 0u;
279 const CharacterIndex characterFirstIndex = *( parameters.glyphsToCharactersBuffer + glyphIndex );
280 const CharacterIndex characterLastIndex = characterFirstIndex + ( hasCharacters ? charactersPerGlyph - 1u : 0u );
282 // Get the line break info for the current character.
283 const LineBreakInfo lineBreakInfo = hasCharacters ? *( parameters.lineBreakInfoBuffer + characterLastIndex ) : TextAbstraction::LINE_NO_BREAK;
285 // Increase the number of characters.
286 tmpLineLayout.numberOfCharacters += charactersPerGlyph;
288 // Increase the number of glyphs.
289 tmpLineLayout.numberOfGlyphs += numberOfGLyphsInGroup;
291 // Check whether is a white space.
292 const Character character = *( parameters.textBuffer + characterFirstIndex );
293 const bool isWhiteSpace = TextAbstraction::IsWhiteSpace( character );
295 // Used to restore the temporal line layout when a single word does not fit in the control's width and is split by character.
296 const float previousTmpLineLength = tmpLineLayout.length;
297 const float previousTmpExtraBearing = tmpExtraBearing;
298 const float previousTmpExtraWidth = tmpExtraWidth;
300 // Get the character's direction.
301 const CharacterDirection characterDirection = ( NULL == parameters.characterDirectionBuffer ) ? false : *( parameters.characterDirectionBuffer + characterFirstIndex );
303 // Increase the accumulated length.
306 // Add the length to the length of white spaces at the end of the line.
307 tmpLineLayout.wsLengthEndOfLine += glyphMetrics.advance; // The advance is used as the width is always zero for the white spaces.
311 // Add as well any previous white space length.
312 tmpLineLayout.length += tmpLineLayout.wsLengthEndOfLine + glyphMetrics.advance;
314 // An extra space may be added to the line for the first and last glyph of the line.
315 // If the bearing of the first glyph is negative, its positive value needs to be added.
316 // If the bearing plus the width of the last glyph is greater than the advance, the difference
317 // needs to be added.
319 if( characterDirection == paragraphDirection )
321 if( RTL == characterDirection )
332 tmpExtraBearing = ( 0.f > glyphMetrics.xBearing ) ? -glyphMetrics.xBearing : 0.f;
345 const float extraWidth = glyphMetrics.xBearing + glyphMetrics.width - glyphMetrics.advance;
346 tmpExtraWidth = ( 0.f < extraWidth ) ? extraWidth : 0.f;
347 tmpExtraWidth = std::max( mPreviousCharacterExtraWidth - glyphMetrics.advance, tmpExtraWidth );
352 if( characterDirection != previousCharacterDirection )
354 if( RTL == characterDirection )
359 const float extraWidth = glyphMetrics.xBearing + glyphMetrics.width - glyphMetrics.advance;
360 tmpExtraWidth = ( 0.f < extraWidth ) ? extraWidth : 0.f;
361 tmpExtraWidth = std::max( mPreviousCharacterExtraWidth - glyphMetrics.advance, tmpExtraWidth );
368 tmpExtraBearing = ( 0.f > glyphMetrics.xBearing ) ? -glyphMetrics.xBearing : 0.f;
371 else if( characterDirection == firstCharacterDirection )
373 if( RTL == characterDirection )
379 tmpExtraBearing = ( 0.f > glyphMetrics.xBearing ) ? -glyphMetrics.xBearing : 0.f;
387 const float extraWidth = glyphMetrics.xBearing + glyphMetrics.width - glyphMetrics.advance;
388 tmpExtraWidth = ( 0.f < extraWidth ) ? extraWidth : 0.f;
389 tmpExtraWidth = std::max( mPreviousCharacterExtraWidth - glyphMetrics.advance, tmpExtraWidth );
394 // Clear the white space length at the end of the line.
395 tmpLineLayout.wsLengthEndOfLine = 0.f;
398 // If calculation is end but wsLengthEndOfLine is exist, it means end of text is space.
399 // Merge remained length.
400 if ( !parameters.ignoreSpaceAfterText && glyphIndex == lastGlyphOfParagraphPlusOne-1 && tmpLineLayout.wsLengthEndOfLine > 0 )
402 tmpLineLayout.length += tmpLineLayout.wsLengthEndOfLine;
403 tmpLineLayout.wsLengthEndOfLine = 0u;
406 // Save the current extra width to compare with the next one
407 mPreviousCharacterExtraWidth = tmpExtraWidth;
409 // Check if the accumulated length fits in the width of the box.
410 if( ( completelyFill || isMultiline ) && !(parameters.ignoreSpaceAfterText && isWhiteSpace) &&
411 ( tmpExtraBearing + lineLayout.length + lineLayout.wsLengthEndOfLine + tmpLineLayout.length + tmpExtraWidth > parameters.boundingBox.width ) )
413 // Current word does not fit in the box's width.
414 if( !oneWordLaidOut || completelyFill )
416 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " Break the word by character\n" );
418 // The word doesn't fit in the control's width. It needs to be split by character.
419 if( tmpLineLayout.numberOfGlyphs > 0u )
421 tmpLineLayout.numberOfCharacters -= charactersPerGlyph;
422 tmpLineLayout.numberOfGlyphs -= numberOfGLyphsInGroup;
423 tmpLineLayout.length = previousTmpLineLength;
424 tmpExtraBearing = previousTmpExtraBearing;
425 tmpExtraWidth = previousTmpExtraWidth;
428 // Add part of the word to the line layout.
429 MergeLineLayout( lineLayout, tmpLineLayout );
433 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " Current word does not fit.\n" );
436 lineLayout.extraBearing = tmpExtraBearing;
437 lineLayout.extraWidth = tmpExtraWidth;
439 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--GetLineLayoutForBox.\n" );
444 if( ( isMultiline || isLastGlyph ) &&
445 ( TextAbstraction::LINE_MUST_BREAK == lineBreakInfo ) )
447 // Must break the line. Update the line layout and return.
448 MergeLineLayout( lineLayout, tmpLineLayout );
450 // Set the next paragraph's direction.
452 ( NULL != parameters.characterDirectionBuffer ) )
454 paragraphDirection = *( parameters.characterDirectionBuffer + 1u + characterLastIndex );
457 lineLayout.extraBearing = tmpExtraBearing;
458 lineLayout.extraWidth = tmpExtraWidth;
460 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " Must break\n" );
461 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--GetLineLayoutForBox\n" );
467 ( TextAbstraction::LINE_ALLOW_BREAK == lineBreakInfo ) )
469 oneWordLaidOut = isWordLaidOut;
470 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " One word laid-out\n" );
472 // Current glyph is the last one of the current word.
473 // Add the temporal layout to the current one.
474 MergeLineLayout( lineLayout, tmpLineLayout );
476 tmpLineLayout.Clear();
479 previousCharacterDirection = characterDirection;
480 glyphIndex += numberOfGLyphsInGroup;
483 lineLayout.extraBearing = tmpExtraBearing;
484 lineLayout.extraWidth = tmpExtraWidth;
486 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--GetLineLayoutForBox\n" );
489 void SetGlyphPositions( const GlyphInfo* const glyphsBuffer,
490 Length numberOfGlyphs,
492 Vector2* glyphPositionsBuffer )
494 // Traverse the glyphs and set the positions.
496 // Check if the x bearing of the first character is negative.
497 // If it has a negative x bearing, it will exceed the boundaries of the actor,
498 // so the penX position needs to be moved to the right.
500 const GlyphInfo& glyph = *glyphsBuffer;
501 float penX = ( 0.f > glyph.xBearing ) ? -glyph.xBearing + outlineWidth : outlineWidth;
504 for( GlyphIndex i = 0u; i < numberOfGlyphs; ++i )
506 const GlyphInfo& glyph = *( glyphsBuffer + i );
507 Vector2& position = *( glyphPositionsBuffer + i );
509 position.x = penX + glyph.xBearing;
510 position.y = -glyph.yBearing;
512 penX += glyph.advance;
517 * @brief Resizes the line buffer.
519 * @param[in,out] lines The vector of lines. Used when the layout is created from scratch.
520 * @param[in,out] newLines The vector of lines used instead of @p lines when the layout is updated.
521 * @param[in,out] linesCapacity The capacity of the vector (either lines or newLines).
522 * @param[in] updateCurrentBuffer Whether the layout is updated.
524 * @return Pointer to either lines or newLines.
526 LineRun* ResizeLinesBuffer( Vector<LineRun>& lines,
527 Vector<LineRun>& newLines,
528 Length& linesCapacity,
529 bool updateCurrentBuffer )
531 LineRun* linesBuffer = NULL;
532 // Reserve more space for the next lines.
534 if( updateCurrentBuffer )
536 newLines.Resize( linesCapacity );
537 linesBuffer = newLines.Begin();
541 lines.Resize( linesCapacity );
542 linesBuffer = lines.Begin();
549 * Ellipsis a line if it exceeds the width's of the bounding box.
551 * @param[in] layoutParameters The parameters needed to layout the text.
552 * @param[in] layout The line layout.
553 * @param[in,out] layoutSize The text's layout size.
554 * @param[in,out] linesBuffer Pointer to the line's buffer.
555 * @param[in,out] glyphPositionsBuffer Pointer to the position's buffer.
556 * @param[in,out] numberOfLines The number of laid-out lines.
557 * @param[in] penY The vertical layout position.
558 * @param[in] currentParagraphDirection The current paragraph's direction.
559 * @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
561 * return Whether the line is ellipsized.
563 bool EllipsisLine( const Parameters& layoutParameters,
564 const LineLayout& layout,
566 LineRun* linesBuffer,
567 Vector2* glyphPositionsBuffer,
568 Length& numberOfLines,
570 CharacterDirection currentParagraphDirection,
571 bool& isAutoScrollEnabled )
573 const bool ellipsis = isAutoScrollEnabled ? ( penY - layout.descender > layoutParameters.boundingBox.height ) :
574 ( ( penY - layout.descender > layoutParameters.boundingBox.height ) ||
575 ( ( mLayout == SINGLE_LINE_BOX ) &&
576 ( layout.length > layoutParameters.boundingBox.width ) ) );
580 isAutoScrollEnabled = false;
581 // Do not layout more lines if ellipsis is enabled.
583 // The last line needs to be completely filled with characters.
584 // Part of a word may be used.
586 LineRun* lineRun = NULL;
587 LineLayout ellipsisLayout;
588 if( 0u != numberOfLines )
590 // Get the last line and layout it again with the 'completelyFill' flag to true.
591 lineRun = linesBuffer + ( numberOfLines - 1u );
593 penY -= layout.ascender - lineRun->descender + lineRun->lineSpacing;
595 ellipsisLayout.glyphIndex = lineRun->glyphRun.glyphIndex;
599 // At least there is space reserved for one line.
600 lineRun = linesBuffer;
602 lineRun->glyphRun.glyphIndex = 0u;
603 ellipsisLayout.glyphIndex = 0u;
608 GetLineLayoutForBox( layoutParameters,
610 currentParagraphDirection,
613 lineRun->glyphRun.numberOfGlyphs = ellipsisLayout.numberOfGlyphs;
614 lineRun->characterRun.characterIndex = ellipsisLayout.characterIndex;
615 lineRun->characterRun.numberOfCharacters = ellipsisLayout.numberOfCharacters;
616 lineRun->width = ellipsisLayout.length;
617 lineRun->extraLength = std::ceil( ( ellipsisLayout.wsLengthEndOfLine > 0.f ) ? ellipsisLayout.wsLengthEndOfLine - ellipsisLayout.extraWidth : 0.f );
618 lineRun->ascender = ellipsisLayout.ascender;
619 lineRun->descender = ellipsisLayout.descender;
620 lineRun->direction = !RTL;
621 lineRun->ellipsis = true;
623 layoutSize.width = layoutParameters.boundingBox.width;
624 if( layoutSize.height < Math::MACHINE_EPSILON_1000 )
626 layoutSize.height += ( lineRun->ascender + -lineRun->descender ) + lineRun->lineSpacing;
629 SetGlyphPositions( layoutParameters.glyphsBuffer + lineRun->glyphRun.glyphIndex,
630 ellipsisLayout.numberOfGlyphs,
631 layoutParameters.outlineWidth,
632 glyphPositionsBuffer + lineRun->glyphRun.glyphIndex - layoutParameters.startGlyphIndex );
639 * @brief Updates the text layout with a new laid-out line.
641 * @param[in] layoutParameters The parameters needed to layout the text.
642 * @param[in] layout The line layout.
643 * @param[in,out] layoutSize The text's layout size.
644 * @param[in,out] linesBuffer Pointer to the line's buffer.
645 * @param[in] index Index to the vector of glyphs.
646 * @param[in,out] numberOfLines The number of laid-out lines.
647 * @param[in] isLastLine Whether the laid-out line is the last one.
649 void UpdateTextLayout( const Parameters& layoutParameters,
650 const LineLayout& layout,
652 LineRun* linesBuffer,
654 Length& numberOfLines,
657 LineRun& lineRun = *( linesBuffer + numberOfLines );
660 lineRun.glyphRun.glyphIndex = index;
661 lineRun.glyphRun.numberOfGlyphs = layout.numberOfGlyphs;
662 lineRun.characterRun.characterIndex = layout.characterIndex;
663 lineRun.characterRun.numberOfCharacters = layout.numberOfCharacters;
664 lineRun.lineSpacing = mDefaultLineSpacing;
666 if( isLastLine && !layoutParameters.isLastNewParagraph )
668 const float width = layout.extraBearing + layout.length + layout.extraWidth + layout.wsLengthEndOfLine;
669 if( MULTI_LINE_BOX == mLayout )
671 lineRun.width = ( width > layoutParameters.boundingBox.width ) ? layoutParameters.boundingBox.width : width;
675 lineRun.width = width;
678 lineRun.extraLength = 0.f;
682 lineRun.width = layout.extraBearing + layout.length + layout.extraWidth;
683 lineRun.extraLength = std::ceil( ( layout.wsLengthEndOfLine > 0.f ) ? layout.wsLengthEndOfLine - layout.extraWidth : 0.f );
686 // Rounds upward to avoid a non integer size.
687 lineRun.width = std::ceil( lineRun.width );
689 lineRun.ascender = layout.ascender;
690 lineRun.descender = layout.descender;
691 lineRun.direction = !RTL;
692 lineRun.ellipsis = false;
694 // Update the actual size.
695 if( lineRun.width > layoutSize.width )
697 layoutSize.width = lineRun.width;
700 layoutSize.height += ( lineRun.ascender + -lineRun.descender ) + lineRun.lineSpacing;
704 * @brief Updates the text layout with the last laid-out line.
706 * @param[in] layoutParameters The parameters needed to layout the text.
707 * @param[in] characterIndex The character index of the line.
708 * @param[in] glyphIndex The glyph index of the line.
709 * @param[in,out] layoutSize The text's layout size.
710 * @param[in,out] linesBuffer Pointer to the line's buffer.
711 * @param[in,out] numberOfLines The number of laid-out lines.
713 void UpdateTextLayout( const Parameters& layoutParameters,
714 CharacterIndex characterIndex,
715 GlyphIndex glyphIndex,
717 LineRun* linesBuffer,
718 Length& numberOfLines )
720 // Need to add a new line with no characters but with height to increase the layoutSize.height
721 const GlyphInfo& glyphInfo = *( layoutParameters.glyphsBuffer + layoutParameters.totalNumberOfGlyphs - 1u );
723 Text::FontMetrics fontMetrics;
724 if( 0u != glyphInfo.fontId )
726 mMetrics->GetFontMetrics( glyphInfo.fontId, fontMetrics );
729 LineRun& lineRun = *( linesBuffer + numberOfLines );
732 lineRun.glyphRun.glyphIndex = glyphIndex;
733 lineRun.glyphRun.numberOfGlyphs = 0u;
734 lineRun.characterRun.characterIndex = characterIndex;
735 lineRun.characterRun.numberOfCharacters = 0u;
737 lineRun.ascender = fontMetrics.ascender;
738 lineRun.descender = fontMetrics.descender;
739 lineRun.extraLength = 0.f;
740 lineRun.alignmentOffset = 0.f;
741 lineRun.direction = !RTL;
742 lineRun.ellipsis = false;
743 lineRun.lineSpacing = mDefaultLineSpacing;
745 layoutSize.height += ( lineRun.ascender + -lineRun.descender ) + lineRun.lineSpacing;
749 * @brief Updates the text's layout size adding the size of the previously laid-out lines.
751 * @param[in] lines The vector of lines (before the new laid-out lines are inserted).
752 * @param[in,out] layoutSize The text's layout size.
754 void UpdateLayoutSize( const Vector<LineRun>& lines,
757 for( Vector<LineRun>::ConstIterator it = lines.Begin(),
762 const LineRun& line = *it;
764 if( line.width > layoutSize.width )
766 layoutSize.width = line.width;
769 layoutSize.height += ( line.ascender + -line.descender ) + line.lineSpacing;
774 * @brief Updates the indices of the character and glyph runs of the lines before the new lines are inserted.
776 * @param[in] layoutParameters The parameters needed to layout the text.
777 * @param[in,out] lines The vector of lines (before the new laid-out lines are inserted).
778 * @param[in] characterOffset The offset to be added to the runs of characters.
779 * @param[in] glyphOffset The offset to be added to the runs of glyphs.
781 void UpdateLineIndexOffsets( const Parameters& layoutParameters,
782 Vector<LineRun>& lines,
783 Length characterOffset,
786 // Update the glyph and character runs.
787 for( Vector<LineRun>::Iterator it = lines.Begin() + layoutParameters.startLineIndex,
794 line.glyphRun.glyphIndex = glyphOffset;
795 line.characterRun.characterIndex = characterOffset;
797 glyphOffset += line.glyphRun.numberOfGlyphs;
798 characterOffset += line.characterRun.numberOfCharacters;
802 bool LayoutText( const Parameters& layoutParameters,
803 Vector<Vector2>& glyphPositions,
804 Vector<LineRun>& lines,
806 bool elideTextEnabled,
807 bool& isAutoScrollEnabled )
809 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "-->LayoutText\n" );
810 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " box size %f, %f\n", layoutParameters.boundingBox.width, layoutParameters.boundingBox.height );
812 if( 0u == layoutParameters.numberOfGlyphs )
814 // Add an extra line if the last character is a new paragraph character and the last line doesn't have zero characters.
815 if( layoutParameters.isLastNewParagraph )
817 Length numberOfLines = lines.Count();
818 if( 0u != numberOfLines )
820 const LineRun& lastLine = *( lines.End() - 1u );
822 if( 0u != lastLine.characterRun.numberOfCharacters )
824 // Need to add a new line with no characters but with height to increase the layoutSize.height
826 Initialize( newLine );
827 lines.PushBack( newLine );
829 UpdateTextLayout( layoutParameters,
830 lastLine.characterRun.characterIndex + lastLine.characterRun.numberOfCharacters,
831 lastLine.glyphRun.glyphIndex + lastLine.glyphRun.numberOfGlyphs,
839 // Calculates the layout size.
840 UpdateLayoutSize( lines,
843 // Rounds upward to avoid a non integer size.
844 layoutSize.height = std::ceil( layoutSize.height );
846 // Nothing else do if there are no glyphs to layout.
850 const GlyphIndex lastGlyphPlusOne = layoutParameters.startGlyphIndex + layoutParameters.numberOfGlyphs;
852 // In a previous layout, an extra line with no characters may have been added if the text ended with a new paragraph character.
853 // This extra line needs to be removed.
854 if( 0u != lines.Count() )
856 Vector<LineRun>::Iterator lastLine = lines.End() - 1u;
858 if( ( 0u == lastLine->characterRun.numberOfCharacters ) &&
859 ( lastGlyphPlusOne == layoutParameters.totalNumberOfGlyphs ) )
861 lines.Remove( lastLine );
865 // Set the first paragraph's direction.
866 CharacterDirection paragraphDirection = ( NULL != layoutParameters.characterDirectionBuffer ) ? *layoutParameters.characterDirectionBuffer : !RTL;
868 // Whether the layout is being updated or set from scratch.
869 const bool updateCurrentBuffer = layoutParameters.numberOfGlyphs < layoutParameters.totalNumberOfGlyphs;
871 Vector2* glyphPositionsBuffer = NULL;
872 Vector<Vector2> newGlyphPositions;
874 LineRun* linesBuffer = NULL;
875 Vector<LineRun> newLines;
877 // Estimate the number of lines.
878 Length linesCapacity = std::max( 1u, layoutParameters.estimatedNumberOfLines );
879 Length numberOfLines = 0u;
881 if( updateCurrentBuffer )
883 newGlyphPositions.Resize( layoutParameters.numberOfGlyphs );
884 glyphPositionsBuffer = newGlyphPositions.Begin();
886 newLines.Resize( linesCapacity );
887 linesBuffer = newLines.Begin();
891 glyphPositionsBuffer = glyphPositions.Begin();
893 lines.Resize( linesCapacity );
894 linesBuffer = lines.Begin();
897 float penY = CalculateLineOffset( lines,
898 layoutParameters.startLineIndex );
900 for( GlyphIndex index = layoutParameters.startGlyphIndex; index < lastGlyphPlusOne; )
902 CharacterDirection currentParagraphDirection = paragraphDirection;
904 // Get the layout for the line.
906 layout.glyphIndex = index;
907 GetLineLayoutForBox( layoutParameters,
912 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " glyph index %d\n", layout.glyphIndex );
913 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " character index %d\n", layout.characterIndex );
914 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " number of glyphs %d\n", layout.numberOfGlyphs );
915 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " number of characters %d\n", layout.numberOfCharacters );
916 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " length %f\n", layout.length );
918 if( 0u == layout.numberOfGlyphs )
920 // The width is too small and no characters are laid-out.
921 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--LayoutText width too small!\n\n" );
923 lines.Resize( numberOfLines );
925 // Rounds upward to avoid a non integer size.
926 layoutSize.height = std::ceil( layoutSize.height );
931 // Set the line position. Discard if ellipsis is enabled and the position exceeds the boundaries
933 penY += layout.ascender;
935 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " pen y %f\n", penY );
937 bool ellipsis = false;
938 if( elideTextEnabled )
940 // Does the ellipsis of the last line.
941 ellipsis = EllipsisLine( layoutParameters,
945 glyphPositionsBuffer,
948 currentParagraphDirection,
949 isAutoScrollEnabled );
954 // No more lines to layout.
959 // Whether the last line has been laid-out.
960 const bool isLastLine = index + layout.numberOfGlyphs == layoutParameters.totalNumberOfGlyphs;
962 if( numberOfLines == linesCapacity )
964 // Reserve more space for the next lines.
965 linesBuffer = ResizeLinesBuffer( lines,
968 updateCurrentBuffer );
971 // Updates the current text's layout with the line's layout.
972 UpdateTextLayout( layoutParameters,
980 const GlyphIndex nextIndex = index + layout.numberOfGlyphs;
982 if( ( nextIndex == layoutParameters.totalNumberOfGlyphs ) &&
983 layoutParameters.isLastNewParagraph &&
984 ( mLayout == MULTI_LINE_BOX ) )
986 // The last character of the text is a new paragraph character.
987 // An extra line with no characters is added to increase the text's height
988 // in order to place the cursor.
990 if( numberOfLines == linesCapacity )
992 // Reserve more space for the next lines.
993 linesBuffer = ResizeLinesBuffer( lines,
996 updateCurrentBuffer );
999 UpdateTextLayout( layoutParameters,
1000 layout.characterIndex + layout.numberOfCharacters,
1001 index + layout.numberOfGlyphs,
1005 } // whether to add a last line.
1007 // Sets the positions of the glyphs.
1008 SetGlyphPositions( layoutParameters.glyphsBuffer + index,
1009 layout.numberOfGlyphs,
1010 layoutParameters.outlineWidth,
1011 glyphPositionsBuffer + index - layoutParameters.startGlyphIndex );
1013 // Updates the vertical pen's position.
1014 penY += -layout.descender + layout.lineSpacing + mDefaultLineSpacing;
1016 // Increase the glyph index.
1019 } // end for() traversing glyphs.
1021 if( updateCurrentBuffer )
1023 glyphPositions.Insert( glyphPositions.Begin() + layoutParameters.startGlyphIndex,
1024 newGlyphPositions.Begin(),
1025 newGlyphPositions.End() );
1026 glyphPositions.Resize( layoutParameters.totalNumberOfGlyphs );
1028 newLines.Resize( numberOfLines );
1030 // Current text's layout size adds only the newly laid-out lines.
1031 // Updates the layout size with the previously laid-out lines.
1032 UpdateLayoutSize( lines,
1035 if( 0u != newLines.Count() )
1037 const LineRun& lastLine = *( newLines.End() - 1u );
1039 const Length characterOffset = lastLine.characterRun.characterIndex + lastLine.characterRun.numberOfCharacters;
1040 const Length glyphOffset = lastLine.glyphRun.glyphIndex + lastLine.glyphRun.numberOfGlyphs;
1042 // Update the indices of the runs before the new laid-out lines are inserted.
1043 UpdateLineIndexOffsets( layoutParameters,
1048 // Insert the lines.
1049 lines.Insert( lines.Begin() + layoutParameters.startLineIndex,
1056 lines.Resize( numberOfLines );
1059 // Rounds upward to avoid a non integer size.
1060 layoutSize.height = std::ceil( layoutSize.height );
1062 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--LayoutText\n\n" );
1067 void ReLayoutRightToLeftLines( const Parameters& layoutParameters,
1068 CharacterIndex startIndex,
1069 Length numberOfCharacters,
1070 Vector<Vector2>& glyphPositions )
1072 const CharacterIndex lastCharacterIndex = startIndex + numberOfCharacters;
1074 // Traverses the paragraphs with right to left characters.
1075 for( LineIndex lineIndex = 0u; lineIndex < layoutParameters.numberOfBidirectionalInfoRuns; ++lineIndex )
1077 const BidirectionalLineInfoRun& bidiLine = *( layoutParameters.lineBidirectionalInfoRunsBuffer + lineIndex );
1079 if( startIndex >= bidiLine.characterRun.characterIndex + bidiLine.characterRun.numberOfCharacters )
1081 // Do not reorder the line if it has been already reordered.
1085 if( bidiLine.characterRun.characterIndex >= lastCharacterIndex )
1087 // Do not reorder the lines after the last requested character.
1091 const CharacterIndex characterVisualIndex = bidiLine.characterRun.characterIndex + *bidiLine.visualToLogicalMap;
1092 const GlyphInfo& glyph = *( layoutParameters.glyphsBuffer + *( layoutParameters.charactersToGlyphsBuffer + characterVisualIndex ) );
1094 float penX = ( 0.f > glyph.xBearing ) ? -glyph.xBearing - layoutParameters.outlineWidth : -layoutParameters.outlineWidth;
1096 Vector2* glyphPositionsBuffer = glyphPositions.Begin();
1098 // Traverses the characters of the right to left paragraph.
1099 for( CharacterIndex characterLogicalIndex = 0u;
1100 characterLogicalIndex < bidiLine.characterRun.numberOfCharacters;
1101 ++characterLogicalIndex )
1103 // Convert the character in the logical order into the character in the visual order.
1104 const CharacterIndex characterVisualIndex = bidiLine.characterRun.characterIndex + *( bidiLine.visualToLogicalMap + characterLogicalIndex );
1106 // Get the number of glyphs of the character.
1107 const Length numberOfGlyphs = *( layoutParameters.glyphsPerCharacterBuffer + characterVisualIndex );
1109 for( GlyphIndex index = 0u; index < numberOfGlyphs; ++index )
1111 // Convert the character in the visual order into the glyph in the visual order.
1112 const GlyphIndex glyphIndex = *( layoutParameters.charactersToGlyphsBuffer + characterVisualIndex ) + index;
1114 DALI_ASSERT_DEBUG( 0u <= glyphIndex && glyphIndex < layoutParameters.totalNumberOfGlyphs );
1116 const GlyphInfo& glyph = *( layoutParameters.glyphsBuffer + glyphIndex );
1117 Vector2& position = *( glyphPositionsBuffer + glyphIndex );
1119 position.x = penX + glyph.xBearing;
1120 penX += glyph.advance;
1126 void Align( const Size& size,
1127 CharacterIndex startIndex,
1128 Length numberOfCharacters,
1129 Text::HorizontalAlignment::Type horizontalAlignment,
1130 Vector<LineRun>& lines,
1131 float& alignmentOffset,
1132 Dali::LayoutDirection::Type layoutDirection,
1133 bool matchSystemLanguageDirection )
1135 const CharacterIndex lastCharacterPlusOne = startIndex + numberOfCharacters;
1137 alignmentOffset = MAX_FLOAT;
1138 // Traverse all lines and align the glyphs.
1139 for( Vector<LineRun>::Iterator it = lines.Begin(), endIt = lines.End();
1143 LineRun& line = *it;
1145 if( line.characterRun.characterIndex < startIndex )
1147 // Do not align lines which have already been aligned.
1151 if( line.characterRun.characterIndex >= lastCharacterPlusOne )
1153 // Do not align lines beyond the last laid-out character.
1157 // Calculate the line's alignment offset accordingly with the align option,
1158 // the box width, line length, and the paragraph's direction.
1159 CalculateHorizontalAlignment( size.width,
1160 horizontalAlignment,
1163 matchSystemLanguageDirection );
1165 // Updates the alignment offset.
1166 alignmentOffset = std::min( alignmentOffset, line.alignmentOffset );
1170 void CalculateHorizontalAlignment( float boxWidth,
1171 HorizontalAlignment::Type horizontalAlignment,
1173 Dali::LayoutDirection::Type layoutDirection,
1174 bool matchSystemLanguageDirection )
1176 line.alignmentOffset = 0.f;
1177 const bool isLineRTL = RTL == line.direction;
1178 // Whether to swap the alignment.
1179 // 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.
1180 bool isLayoutRTL = isLineRTL;
1181 float lineLength = line.width;
1183 // match align for system language direction
1184 if( matchSystemLanguageDirection )
1186 // Swap the alignment type if the line is right to left.
1187 isLayoutRTL = layoutDirection == LayoutDirection::RIGHT_TO_LEFT;
1189 // Calculate the horizontal line offset.
1190 switch( horizontalAlignment )
1192 case HorizontalAlignment::BEGIN:
1198 lineLength += line.extraLength;
1201 line.alignmentOffset = boxWidth - lineLength;
1205 line.alignmentOffset = 0.f;
1209 // 'Remove' the white spaces at the end of the line (which are at the beginning in visual order)
1210 line.alignmentOffset -= line.extraLength;
1215 case HorizontalAlignment::CENTER:
1217 line.alignmentOffset = 0.5f * ( boxWidth - lineLength );
1221 line.alignmentOffset -= line.extraLength;
1224 line.alignmentOffset = floorf( line.alignmentOffset ); // try to avoid pixel alignment.
1227 case HorizontalAlignment::END:
1231 line.alignmentOffset = 0.f;
1235 // 'Remove' the white spaces at the end of the line (which are at the beginning in visual order)
1236 line.alignmentOffset -= line.extraLength;
1243 lineLength += line.extraLength;
1246 line.alignmentOffset = boxWidth - lineLength;
1253 void Initialize( LineRun& line )
1255 line.glyphRun.glyphIndex = 0u;
1256 line.glyphRun.numberOfGlyphs = 0u;
1257 line.characterRun.characterIndex = 0u;
1258 line.characterRun.numberOfCharacters = 0u;
1260 line.ascender = 0.f;
1261 line.descender = 0.f;
1262 line.extraLength = 0.f;
1263 line.alignmentOffset = 0.f;
1264 line.direction = !RTL;
1265 line.ellipsis = false;
1266 line.lineSpacing = mDefaultLineSpacing;
1271 float mDefaultLineSpacing;
1272 float mPreviousCharacterExtraWidth;
1274 IntrusivePtr<Metrics> mMetrics;
1280 mImpl = new Engine::Impl();
1288 void Engine::SetMetrics( MetricsPtr& metrics )
1290 mImpl->mMetrics = metrics;
1293 void Engine::SetLayout( Type layout )
1295 mImpl->mLayout = layout;
1298 Engine::Type Engine::GetLayout() const
1300 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "GetLayout[%d]\n", mImpl->mLayout);
1301 return mImpl->mLayout;
1304 void Engine::SetCursorWidth( int width )
1306 mImpl->mCursorWidth = static_cast<float>( width );
1309 int Engine::GetCursorWidth() const
1311 return static_cast<int>( mImpl->mCursorWidth );
1314 bool Engine::LayoutText( const Parameters& layoutParameters,
1315 Vector<Vector2>& glyphPositions,
1316 Vector<LineRun>& lines,
1318 bool elideTextEnabled,
1319 bool& isAutoScrollEnabled )
1321 return mImpl->LayoutText( layoutParameters,
1326 isAutoScrollEnabled );
1329 void Engine::ReLayoutRightToLeftLines( const Parameters& layoutParameters,
1330 CharacterIndex startIndex,
1331 Length numberOfCharacters,
1332 Vector<Vector2>& glyphPositions )
1334 mImpl->ReLayoutRightToLeftLines( layoutParameters,
1340 void Engine::Align( const Size& size,
1341 CharacterIndex startIndex,
1342 Length numberOfCharacters,
1343 Text::HorizontalAlignment::Type horizontalAlignment,
1344 Vector<LineRun>& lines,
1345 float& alignmentOffset,
1346 Dali::LayoutDirection::Type layoutDirection,
1347 bool matchSystemLanguageDirection )
1352 horizontalAlignment,
1356 matchSystemLanguageDirection );
1359 void Engine::SetDefaultLineSpacing( float lineSpacing )
1361 mImpl->mDefaultLineSpacing = lineSpacing;
1364 float Engine::GetDefaultLineSpacing() const
1366 return mImpl->mDefaultLineSpacing;
1369 } // namespace Layout
1373 } // namespace Toolkit