2 * Copyright (c) 2015 Samsung Electronics Co., Ltd.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
19 #include <dali-toolkit/internal/text/layouts/layout-engine.h>
23 #include <dali/integration-api/debug.h>
24 #include <dali/devel-api/text-abstraction/font-client.h>
27 #include <dali-toolkit/internal/text/bidirectional-line-info-run.h>
28 #include <dali-toolkit/internal/text/glyph-metrics-helper.h>
29 #include <dali-toolkit/internal/text/layouts/layout-parameters.h>
43 #if defined(DEBUG_ENABLED)
44 Debug::Filter* gLogFilter = Debug::Filter::New(Debug::Concise, true, "LOG_TEXT_LAYOUT");
47 const float MAX_FLOAT = std::numeric_limits<float>::max();
48 const bool RTL = true;
49 const float CURSOR_WIDTH = 1.f;
54 * @brief Stores temporary layout info of the line.
62 numberOfCharacters( 0u ),
66 wsLengthEndOfLine( 0.f ),
68 descender( MAX_FLOAT )
79 numberOfCharacters = 0u;
83 wsLengthEndOfLine = 0.f;
85 descender = MAX_FLOAT;
88 GlyphIndex glyphIndex; ///< Index of the first glyph to be laid-out.
89 CharacterIndex characterIndex; ///< Index of the first character to be laid-out.
90 Length numberOfGlyphs; ///< The number of glyph which fit in one line.
91 Length numberOfCharacters; ///< The number of characters which fit in one line.
92 float length; ///< The addition of the advance metric of all the glyphs which fit in one line.
93 float extraBearing; ///< The extra width to be added to the line's length when the bearing of the first glyph is negative.
94 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.
95 float wsLengthEndOfLine; ///< The length of the white spaces at the end of the line.
96 float ascender; ///< The maximum ascender of all fonts in the line.
97 float descender; ///< The minimum descender of all fonts in the line.
100 struct LayoutEngine::Impl
103 : mLayout( LayoutEngine::SINGLE_LINE_BOX ),
104 mHorizontalAlignment( LayoutEngine::HORIZONTAL_ALIGN_BEGIN ),
105 mVerticalAlignment( LayoutEngine::VERTICAL_ALIGN_TOP ),
106 mCursorWidth( CURSOR_WIDTH ),
107 mEllipsisEnabled( false )
112 * @brief Updates the line ascender and descender with the metrics of a new font.
114 * @param[in] fontId The id of the new font.
115 * @param[in,out] lineLayout The line layout.
117 void UpdateLineHeight( FontId fontId, LineLayout& lineLayout )
119 Text::FontMetrics fontMetrics;
120 mMetrics->GetFontMetrics( fontId, fontMetrics );
122 // Sets the maximum ascender.
123 if( fontMetrics.ascender > lineLayout.ascender )
125 lineLayout.ascender = fontMetrics.ascender;
128 // Sets the minimum descender.
129 if( fontMetrics.descender < lineLayout.descender )
131 lineLayout.descender = fontMetrics.descender;
136 * @brief Merges a temporary line layout into the line layout.
138 * @param[in,out] lineLayout The line layout.
139 * @param[in] tmpLineLayout A temporary line layout.
141 void MergeLineLayout( LineLayout& lineLayout,
142 const LineLayout& tmpLineLayout )
144 lineLayout.numberOfCharacters += tmpLineLayout.numberOfCharacters;
145 lineLayout.numberOfGlyphs += tmpLineLayout.numberOfGlyphs;
146 lineLayout.length += tmpLineLayout.length;
148 if( 0.f < tmpLineLayout.length )
150 lineLayout.length += lineLayout.wsLengthEndOfLine;
152 lineLayout.wsLengthEndOfLine = tmpLineLayout.wsLengthEndOfLine;
156 lineLayout.wsLengthEndOfLine += tmpLineLayout.wsLengthEndOfLine;
159 if( tmpLineLayout.ascender > lineLayout.ascender )
161 lineLayout.ascender = tmpLineLayout.ascender;
164 if( tmpLineLayout.descender < lineLayout.descender )
166 lineLayout.descender = tmpLineLayout.descender;
171 * Retrieves the line layout for a given box width.
173 * @note This method lais out text as it were left to right. At this point is not possible to reorder the line
174 * because the number of characters of the line is not known (one of the responsabilities of this method
175 * is calculate that). Due to glyph's 'x' bearing, width and advance, when right to left or mixed right to left
176 * and left to right text is laid-out, it can be small differences in the line length. One solution is to
177 * reorder and re-lay out the text after this method and add or remove one extra glyph if needed. However,
178 * this method calculates which are the first and last glyphs of the line (the ones that causes the
179 * differences). This is a good point to check if there is problems with the text exceeding the boundaries
180 * of the control when there is right to left text.
182 * @param[in] parameters The layout parameters.
183 * @param[out] lineLayout The line layout.
184 * @param[in,out] paragraphDirection in: the current paragraph's direction, out: the next paragraph's direction. Is set after a must break.
185 * @param[in] completelyFill Whether to completely fill the line ( even if the last word exceeds the boundaries ).
187 void GetLineLayoutForBox( const LayoutParameters& parameters,
188 LineLayout& lineLayout,
189 CharacterDirection& paragraphDirection,
190 bool completelyFill )
192 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "-->GetLineLayoutForBox\n" );
193 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " initial glyph index : %d\n", lineLayout.glyphIndex );
194 // Stores temporary line layout which has not been added to the final line layout.
195 LineLayout tmpLineLayout;
197 const bool isMultiline = mLayout == MULTI_LINE_BOX;
199 // The last glyph to be laid-out.
200 const GlyphIndex lastGlyphOfParagraphPlusOne = parameters.startGlyphIndex + parameters.numberOfGlyphs;
202 // If the first glyph has a negative bearing its absolute value needs to be added to the line length.
203 // In the case the line starts with a right to left character, if the width is longer than the advance,
204 // the difference needs to be added to the line length.
206 // Check whether the first glyph comes from a character that is shaped in multiple glyphs.
207 const Length numberOfGLyphsInGroup = GetNumberOfGlyphsOfGroup( lineLayout.glyphIndex,
208 lastGlyphOfParagraphPlusOne,
209 parameters.charactersPerGlyphBuffer );
211 GlyphMetrics glyphMetrics;
212 GetGlyphsMetrics( lineLayout.glyphIndex,
213 numberOfGLyphsInGroup,
215 parameters.glyphsBuffer,
218 // Set the direction of the first character of the line.
219 lineLayout.characterIndex = *( parameters.glyphsToCharactersBuffer + lineLayout.glyphIndex );
220 const CharacterDirection firstCharacterDirection = ( NULL == parameters.characterDirectionBuffer ) ? false : *( parameters.characterDirectionBuffer + lineLayout.characterIndex );
221 CharacterDirection previousCharacterDirection = firstCharacterDirection;
223 const float extraWidth = glyphMetrics.xBearing + glyphMetrics.width - glyphMetrics.advance;
224 float tmpExtraWidth = ( 0.f < extraWidth ) ? extraWidth : 0.f;
226 float tmpExtraBearing = ( 0.f > glyphMetrics.xBearing ) ? -glyphMetrics.xBearing : 0.f;
228 tmpLineLayout.length += mCursorWidth; // Added to give some space to the cursor.
230 // Calculate the line height if there is no characters.
231 FontId lastFontId = glyphMetrics.fontId;
232 UpdateLineHeight( lastFontId, tmpLineLayout );
234 bool oneWordLaidOut = false;
236 for( GlyphIndex glyphIndex = lineLayout.glyphIndex;
237 glyphIndex < lastGlyphOfParagraphPlusOne; )
239 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " glyph index : %d\n", glyphIndex );
241 // Check whether this glyph comes from a character that is shaped in multiple glyphs.
242 const Length numberOfGLyphsInGroup = GetNumberOfGlyphsOfGroup( glyphIndex,
243 lastGlyphOfParagraphPlusOne,
244 parameters.charactersPerGlyphBuffer );
246 GlyphMetrics glyphMetrics;
247 GetGlyphsMetrics( glyphIndex,
248 numberOfGLyphsInGroup,
250 parameters.glyphsBuffer,
253 const bool isLastGlyph = glyphIndex + numberOfGLyphsInGroup == parameters.totalNumberOfGlyphs;
255 // Check if the font of the current glyph is the same of the previous one.
256 // If it's different the ascender and descender need to be updated.
257 if( lastFontId != glyphMetrics.fontId )
259 UpdateLineHeight( glyphMetrics.fontId, tmpLineLayout );
260 lastFontId = glyphMetrics.fontId;
263 // Get the character indices for the current glyph. The last character index is needed
264 // because there are glyphs formed by more than one character but their break info is
265 // given only for the last character.
266 const Length charactersPerGlyph = *( parameters.charactersPerGlyphBuffer + glyphIndex + numberOfGLyphsInGroup - 1u );
267 const bool hasCharacters = charactersPerGlyph > 0u;
268 const CharacterIndex characterFirstIndex = *( parameters.glyphsToCharactersBuffer + glyphIndex );
269 const CharacterIndex characterLastIndex = characterFirstIndex + ( hasCharacters ? charactersPerGlyph - 1u : 0u );
271 // Get the line break info for the current character.
272 const LineBreakInfo lineBreakInfo = hasCharacters ? *( parameters.lineBreakInfoBuffer + characterLastIndex ) : TextAbstraction::LINE_NO_BREAK;
274 // Get the word break info for the current character.
275 const WordBreakInfo wordBreakInfo = *( parameters.wordBreakInfoBuffer + characterLastIndex );
277 // Increase the number of characters.
278 tmpLineLayout.numberOfCharacters += charactersPerGlyph;
280 // Increase the number of glyphs.
281 tmpLineLayout.numberOfGlyphs += numberOfGLyphsInGroup;
283 // Check whether is a white space.
284 const Character character = *( parameters.textBuffer + characterFirstIndex );
285 const bool isWhiteSpace = TextAbstraction::IsWhiteSpace( character );
287 // Used to restore the temporal line layout when a single word does not fit in the control's width and is split by character.
288 const float previousTmpLineLength = tmpLineLayout.length;
289 const float previousTmpExtraBearing = tmpExtraBearing;
290 const float previousTmpExtraWidth = tmpExtraWidth;
292 // Get the character's direction.
293 const CharacterDirection characterDirection = ( NULL == parameters.characterDirectionBuffer ) ? false : *( parameters.characterDirectionBuffer + characterFirstIndex );
295 // Increase the accumulated length.
298 // Add the length to the length of white spaces at the end of the line.
299 tmpLineLayout.wsLengthEndOfLine += glyphMetrics.advance; // The advance is used as the width is always zero for the white spaces.
303 // Add as well any previous white space length.
304 tmpLineLayout.length += tmpLineLayout.wsLengthEndOfLine + glyphMetrics.advance;
306 // An extra space may be added to the line for the first and last glyph of the line.
307 // If the bearing of the first glyph is negative, its positive value needs to be added.
308 // If the bearing plus the width of the last glyph is greater than the advance, the difference
309 // needs to be added.
311 if( characterDirection == paragraphDirection )
313 if( RTL == characterDirection )
324 tmpExtraBearing = ( 0.f > glyphMetrics.xBearing ) ? -glyphMetrics.xBearing : 0.f;
337 const float extraWidth = glyphMetrics.xBearing + glyphMetrics.width - glyphMetrics.advance;
338 tmpExtraWidth = ( 0.f < extraWidth ) ? extraWidth : 0.f;
343 if( characterDirection != previousCharacterDirection )
345 if( RTL == characterDirection )
350 const float extraWidth = glyphMetrics.xBearing + glyphMetrics.width - glyphMetrics.advance;
351 tmpExtraWidth = ( 0.f < extraWidth ) ? extraWidth : 0.f;
358 tmpExtraBearing = ( 0.f > glyphMetrics.xBearing ) ? -glyphMetrics.xBearing : 0.f;
361 else if( characterDirection == firstCharacterDirection )
363 if( RTL == characterDirection )
369 tmpExtraBearing = ( 0.f > glyphMetrics.xBearing ) ? -glyphMetrics.xBearing : 0.f;
377 const float extraWidth = glyphMetrics.xBearing + glyphMetrics.width - glyphMetrics.advance;
378 tmpExtraWidth = ( 0.f < extraWidth ) ? extraWidth : 0.f;
383 // Clear the white space length at the end of the line.
384 tmpLineLayout.wsLengthEndOfLine = 0.f;
387 // Check if the accumulated length fits in the width of the box.
388 if( ( completelyFill || isMultiline ) && !isWhiteSpace &&
389 ( tmpExtraBearing + lineLayout.length + lineLayout.wsLengthEndOfLine + tmpLineLayout.length + tmpExtraWidth > parameters.boundingBox.width ) )
391 // Current word does not fit in the box's width.
392 if( !oneWordLaidOut || completelyFill )
394 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " Break the word by character\n" );
396 // The word doesn't fit in the control's width. It needs to be split by character.
397 if( tmpLineLayout.numberOfGlyphs > 0u )
399 tmpLineLayout.numberOfCharacters -= charactersPerGlyph;
400 tmpLineLayout.numberOfGlyphs -= numberOfGLyphsInGroup;
401 tmpLineLayout.length = previousTmpLineLength;
402 tmpExtraBearing = previousTmpExtraBearing;
403 tmpExtraWidth = previousTmpExtraWidth;
406 // Add part of the word to the line layout.
407 MergeLineLayout( lineLayout, tmpLineLayout );
411 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " Current word does not fit.\n" );
414 lineLayout.extraBearing = tmpExtraBearing;
415 lineLayout.extraWidth = tmpExtraWidth;
417 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--GetLineLayoutForBox.\n" );
422 if( ( isMultiline || isLastGlyph ) &&
423 ( TextAbstraction::LINE_MUST_BREAK == lineBreakInfo ) )
425 // Must break the line. Update the line layout and return.
426 MergeLineLayout( lineLayout, tmpLineLayout );
428 // Set the next paragraph's direction.
430 ( NULL != parameters.characterDirectionBuffer ) )
432 paragraphDirection = *( parameters.characterDirectionBuffer + 1u + characterLastIndex );
435 lineLayout.extraBearing = tmpExtraBearing;
436 lineLayout.extraWidth = tmpExtraWidth;
438 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " Must break\n" );
439 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--GetLineLayoutForBox\n" );
445 ( TextAbstraction::WORD_BREAK == wordBreakInfo ) )
447 oneWordLaidOut = true;
448 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " One word laid-out\n" );
450 // Current glyph is the last one of the current word.
451 // Add the temporal layout to the current one.
452 MergeLineLayout( lineLayout, tmpLineLayout );
454 tmpLineLayout.Clear();
457 previousCharacterDirection = characterDirection;
458 glyphIndex += numberOfGLyphsInGroup;
461 lineLayout.extraBearing = tmpExtraBearing;
462 lineLayout.extraWidth = tmpExtraWidth;
464 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--GetLineLayoutForBox\n" );
468 * @brief Calculates the vertical offset to add to the new laid-out glyphs.
470 * @pre @p lineIndex must be between 0 and the number of lines (both inclusive).
472 * @param[in] lines The previously laid-out lines.
473 * @param[in] lineIndex Index to the line where the new laid-out lines are inserted.
475 * @return The vertical offset of the lines starting from the beginning to the line @p lineIndex.
477 float SetParagraphOffset( const Vector<LineRun>& lines,
478 LineIndex lineIndex )
482 for( Vector<LineRun>::ConstIterator it = lines.Begin(),
483 endIt = lines.Begin() + lineIndex;
487 const LineRun& line = *it;
489 offset += line.ascender + -line.descender;
495 void SetGlyphPositions( const GlyphInfo* const glyphsBuffer,
496 Length numberOfGlyphs,
497 Vector2* glyphPositionsBuffer )
499 // Traverse the glyphs and set the positions.
501 // Check if the x bearing of the first character is negative.
502 // If it has a negative x bearing, it will exceed the boundaries of the actor,
503 // so the penX position needs to be moved to the right.
505 const GlyphInfo& glyph = *glyphsBuffer;
506 float penX = ( 0.f > glyph.xBearing ) ? -glyph.xBearing : 0.f;
508 for( GlyphIndex i = 0u; i < numberOfGlyphs; ++i )
510 const GlyphInfo& glyph = *( glyphsBuffer + i );
511 Vector2& position = *( glyphPositionsBuffer + i );
513 position.x = penX + glyph.xBearing;
514 position.y = -glyph.yBearing;
516 penX += glyph.advance;
521 * @brief Resizes the line buffer.
523 * @param[in,out] lines The vector of lines. Used when the layout is created from scratch.
524 * @param[in,out] newLines The vector of lines used instead of @p lines when the layout is updated.
525 * @param[in,out] linesCapacity The capacity of the vector (either lines or newLines).
526 * @param[in] updateCurrentBuffer Whether the layout is updated.
528 * @return Pointer to either lines or newLines.
530 LineRun* ResizeLinesBuffer( Vector<LineRun>& lines,
531 Vector<LineRun>& newLines,
532 Length& linesCapacity,
533 bool updateCurrentBuffer )
535 LineRun* linesBuffer = NULL;
536 // Reserve more space for the next lines.
538 if( updateCurrentBuffer )
540 newLines.Resize( linesCapacity );
541 linesBuffer = newLines.Begin();
545 lines.Resize( linesCapacity );
546 linesBuffer = lines.Begin();
553 * Ellipsis a line if it exceeds the width's of the bounding box.
555 * @param[in] layoutParameters The parameters needed to layout the text.
556 * @param[in] layout The line layout.
557 * @param[in,out] layoutSize The text's layout size.
558 * @param[in,out] linesBuffer Pointer to the line's buffer.
559 * @param[in,out] glyphPositionsBuffer Pointer to the position's buffer.
560 * @param[in,out] numberOfLines The number of laid-out lines.
561 * @param[in] penY The vertical layout position.
562 * @param[in] currentParagraphDirection The current paragraph's direction.
564 * return Whether the line is ellipsized.
566 bool EllipsisLine( const LayoutParameters& layoutParameters,
567 const LineLayout& layout,
569 LineRun* linesBuffer,
570 Vector2* glyphPositionsBuffer,
571 Length& numberOfLines,
573 CharacterDirection currentParagraphDirection )
575 const bool ellipsis = ( ( penY - layout.descender > layoutParameters.boundingBox.height ) ||
576 ( ( mLayout == SINGLE_LINE_BOX ) &&
577 ( layout.extraBearing + layout.length + layout.extraWidth > layoutParameters.boundingBox.width ) ) );
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;
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 = ( 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 layoutSize.height += ( lineRun->ascender + -lineRun->descender );
626 SetGlyphPositions( layoutParameters.glyphsBuffer + lineRun->glyphRun.glyphIndex,
627 ellipsisLayout.numberOfGlyphs,
628 glyphPositionsBuffer + lineRun->glyphRun.glyphIndex - layoutParameters.startGlyphIndex );
635 * @brief Updates the text layout with a new laid-out line.
637 * @param[in] layoutParameters The parameters needed to layout the text.
638 * @param[in] layout The line layout.
639 * @param[in,out] layoutSize The text's layout size.
640 * @param[in,out] linesBuffer Pointer to the line's buffer.
641 * @param[in] index Index to the vector of glyphs.
642 * @param[in,out] numberOfLines The number of laid-out lines.
643 * @param[in] isLastLine Whether the laid-out line is the last one.
645 void UpdateTextLayout( const LayoutParameters& layoutParameters,
646 const LineLayout& layout,
648 LineRun* linesBuffer,
650 Length& numberOfLines,
653 LineRun& lineRun = *( linesBuffer + numberOfLines );
656 lineRun.glyphRun.glyphIndex = index;
657 lineRun.glyphRun.numberOfGlyphs = layout.numberOfGlyphs;
658 lineRun.characterRun.characterIndex = layout.characterIndex;
659 lineRun.characterRun.numberOfCharacters = layout.numberOfCharacters;
660 if( isLastLine && !layoutParameters.isLastNewParagraph )
662 const float width = layout.extraBearing + layout.length + layout.extraWidth + layout.wsLengthEndOfLine;
663 if( MULTI_LINE_BOX == mLayout )
665 lineRun.width = ( width > layoutParameters.boundingBox.width ) ? layoutParameters.boundingBox.width : width;
669 lineRun.width = width;
672 lineRun.extraLength = 0.f;
676 lineRun.width = layout.extraBearing + layout.length + layout.extraWidth;
677 lineRun.extraLength = ( layout.wsLengthEndOfLine > 0.f ) ? layout.wsLengthEndOfLine - layout.extraWidth : 0.f;
679 lineRun.ascender = layout.ascender;
680 lineRun.descender = layout.descender;
681 lineRun.direction = !RTL;
682 lineRun.ellipsis = false;
684 // Update the actual size.
685 if( lineRun.width > layoutSize.width )
687 layoutSize.width = lineRun.width;
690 layoutSize.height += ( lineRun.ascender + -lineRun.descender );
694 * @brief Updates the text layout with the last laid-out line.
696 * @param[in] layoutParameters The parameters needed to layout the text.
697 * @param[in] characterIndex The character index of the line.
698 * @param[in] glyphIndex The glyph index of the line.
699 * @param[in,out] layoutSize The text's layout size.
700 * @param[in,out] linesBuffer Pointer to the line's buffer.
701 * @param[in,out] numberOfLines The number of laid-out lines.
703 void UpdateTextLayout( const LayoutParameters& layoutParameters,
704 CharacterIndex characterIndex,
705 GlyphIndex glyphIndex,
707 LineRun* linesBuffer,
708 Length& numberOfLines )
710 // Need to add a new line with no characters but with height to increase the layoutSize.height
711 const GlyphInfo& glyphInfo = *( layoutParameters.glyphsBuffer + layoutParameters.totalNumberOfGlyphs - 1u );
713 Text::FontMetrics fontMetrics;
714 mMetrics->GetFontMetrics( glyphInfo.fontId, fontMetrics );
716 LineRun& lineRun = *( linesBuffer + numberOfLines );
719 lineRun.glyphRun.glyphIndex = glyphIndex;
720 lineRun.glyphRun.numberOfGlyphs = 0u;
721 lineRun.characterRun.characterIndex = characterIndex;
722 lineRun.characterRun.numberOfCharacters = 0u;
724 lineRun.ascender = fontMetrics.ascender;
725 lineRun.descender = fontMetrics.descender;
726 lineRun.extraLength = 0.f;
727 lineRun.alignmentOffset = 0.f;
728 lineRun.direction = !RTL;
729 lineRun.ellipsis = false;
731 layoutSize.height += ( lineRun.ascender + -lineRun.descender );
735 * @brief Updates the text's layout size adding the size of the previously laid-out lines.
737 * @param[in] lines The vector of lines (before the new laid-out lines are inserted).
738 * @param[in,out] layoutSize The text's layout size.
740 void UpdateLayoutSize( const Vector<LineRun>& lines,
743 for( Vector<LineRun>::ConstIterator it = lines.Begin(),
748 const LineRun& line = *it;
750 if( line.width > layoutSize.width )
752 layoutSize.width = line.width;
755 layoutSize.height += ( line.ascender + -line.descender );
760 * @brief Updates the indices of the character and glyph runs of the lines before the new lines are inserted.
762 * @param[in] layoutParameters The parameters needed to layout the text.
763 * @param[in,out] lines The vector of lines (before the new laid-out lines are inserted).
764 * @param[in] characterOffset The offset to be added to the runs of characters.
765 * @param[in] glyphOffset The offset to be added to the runs of glyphs.
767 void UpdateLineIndexOffsets( const LayoutParameters& layoutParameters,
768 Vector<LineRun>& lines,
769 Length characterOffset,
772 // Update the glyph and character runs.
773 for( Vector<LineRun>::Iterator it = lines.Begin() + layoutParameters.startLineIndex,
780 line.glyphRun.glyphIndex = glyphOffset;
781 line.characterRun.characterIndex = characterOffset;
783 glyphOffset += line.glyphRun.numberOfGlyphs;
784 characterOffset += line.characterRun.numberOfCharacters;
788 bool LayoutText( const LayoutParameters& layoutParameters,
789 Vector<Vector2>& glyphPositions,
790 Vector<LineRun>& lines,
793 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "-->LayoutText\n" );
794 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " box size %f, %f\n", layoutParameters.boundingBox.width, layoutParameters.boundingBox.height );
796 if( 0u == layoutParameters.numberOfGlyphs )
798 // Add an extra line if the last character is a new paragraph character and the last line doesn't have zero characters.
799 if( layoutParameters.isLastNewParagraph )
801 Length numberOfLines = lines.Count();
802 if( 0u != numberOfLines )
804 const LineRun& lastLine = *( lines.End() - 1u );
806 if( 0u != lastLine.characterRun.numberOfCharacters )
808 // Need to add a new line with no characters but with height to increase the layoutSize.height
810 Initialize( newLine );
811 lines.PushBack( newLine );
813 UpdateTextLayout( layoutParameters,
814 lastLine.characterRun.characterIndex + lastLine.characterRun.numberOfCharacters,
815 lastLine.glyphRun.glyphIndex + lastLine.glyphRun.numberOfGlyphs,
823 // Nothing else do if there are no glyphs to layout.
827 const GlyphIndex lastGlyphPlusOne = layoutParameters.startGlyphIndex + layoutParameters.numberOfGlyphs;
829 // In a previous layout, an extra line with no characters may have been added if the text ended with a new paragraph character.
830 // This extra line needs to be removed.
831 if( 0u != lines.Count() )
833 Vector<LineRun>::Iterator lastLine = lines.End() - 1u;
835 if( ( 0u == lastLine->characterRun.numberOfCharacters ) &&
836 ( lastGlyphPlusOne == layoutParameters.totalNumberOfGlyphs ) )
838 lines.Remove( lastLine );
842 // Set the first paragraph's direction.
843 CharacterDirection paragraphDirection = ( NULL != layoutParameters.characterDirectionBuffer ) ? *layoutParameters.characterDirectionBuffer : !RTL;
845 // Whether the layout is being updated or set from scratch.
846 const bool updateCurrentBuffer = layoutParameters.numberOfGlyphs < layoutParameters.totalNumberOfGlyphs;
848 Vector2* glyphPositionsBuffer = NULL;
849 Vector<Vector2> newGlyphPositions;
851 LineRun* linesBuffer = NULL;
852 Vector<LineRun> newLines;
854 // Estimate the number of lines.
855 Length linesCapacity = std::max( 1u, layoutParameters.estimatedNumberOfLines );
856 Length numberOfLines = 0u;
858 if( updateCurrentBuffer )
860 newGlyphPositions.Resize( layoutParameters.numberOfGlyphs );
861 glyphPositionsBuffer = newGlyphPositions.Begin();
863 newLines.Resize( linesCapacity );
864 linesBuffer = newLines.Begin();
868 glyphPositionsBuffer = glyphPositions.Begin();
870 lines.Resize( linesCapacity );
871 linesBuffer = lines.Begin();
874 float penY = SetParagraphOffset( lines,
875 layoutParameters.startLineIndex );
877 for( GlyphIndex index = layoutParameters.startGlyphIndex; index < lastGlyphPlusOne; )
879 CharacterDirection currentParagraphDirection = paragraphDirection;
881 // Get the layout for the line.
883 layout.glyphIndex = index;
884 GetLineLayoutForBox( layoutParameters,
889 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " glyph index %d\n", layout.glyphIndex );
890 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " character index %d\n", layout.characterIndex );
891 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " number of glyphs %d\n", layout.numberOfGlyphs );
892 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " number of characters %d\n", layout.numberOfCharacters );
893 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " length %f\n", layout.length );
895 if( 0u == layout.numberOfGlyphs )
897 // The width is too small and no characters are laid-out.
898 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--LayoutText width too small!\n\n" );
900 lines.Resize( numberOfLines );
904 // Set the line position. Discard if ellipsis is enabled and the position exceeds the boundaries
906 penY += layout.ascender;
908 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " pen y %f\n", penY );
910 bool ellipsis = false;
911 if( mEllipsisEnabled )
913 // Does the ellipsis of the last line.
914 ellipsis = EllipsisLine( layoutParameters,
918 glyphPositionsBuffer,
921 currentParagraphDirection );
926 // No more lines to layout.
931 // Whether the last line has been laid-out.
932 const bool isLastLine = index + layout.numberOfGlyphs == layoutParameters.totalNumberOfGlyphs;
934 if( numberOfLines == linesCapacity )
936 // Reserve more space for the next lines.
937 linesBuffer = ResizeLinesBuffer( lines,
940 updateCurrentBuffer );
943 // Updates the current text's layout with the line's layout.
944 UpdateTextLayout( layoutParameters,
952 const GlyphIndex nextIndex = index + layout.numberOfGlyphs;
954 if( ( nextIndex == layoutParameters.totalNumberOfGlyphs ) &&
955 layoutParameters.isLastNewParagraph &&
956 ( mLayout == MULTI_LINE_BOX ) )
958 // The last character of the text is a new paragraph character.
959 // An extra line with no characters is added to increase the text's height
960 // in order to place the cursor.
962 if( numberOfLines == linesCapacity )
964 // Reserve more space for the next lines.
965 linesBuffer = ResizeLinesBuffer( lines,
968 updateCurrentBuffer );
971 UpdateTextLayout( layoutParameters,
972 layout.characterIndex + layout.numberOfCharacters,
973 index + layout.numberOfGlyphs,
977 } // whether to add a last line.
979 // Sets the positions of the glyphs.
980 SetGlyphPositions( layoutParameters.glyphsBuffer + index,
981 layout.numberOfGlyphs,
982 glyphPositionsBuffer + index - layoutParameters.startGlyphIndex );
984 // Updates the vertical pen's position.
985 penY += -layout.descender;
987 // Increase the glyph index.
990 } // end for() traversing glyphs.
992 if( updateCurrentBuffer )
994 glyphPositions.Insert( glyphPositions.Begin() + layoutParameters.startGlyphIndex,
995 newGlyphPositions.Begin(),
996 newGlyphPositions.End() );
997 glyphPositions.Resize( layoutParameters.totalNumberOfGlyphs );
999 newLines.Resize( numberOfLines );
1001 // Current text's layout size adds only the newly laid-out lines.
1002 // Updates the layout size with the previously laid-out lines.
1003 UpdateLayoutSize( lines,
1006 if( 0u != newLines.Count() )
1008 const LineRun& lastLine = *( newLines.End() - 1u );
1010 const Length characterOffset = lastLine.characterRun.characterIndex + lastLine.characterRun.numberOfCharacters;
1011 const Length glyphOffset = lastLine.glyphRun.glyphIndex + lastLine.glyphRun.numberOfGlyphs;
1013 // Update the indices of the runs before the new laid-out lines are inserted.
1014 UpdateLineIndexOffsets( layoutParameters,
1019 // Insert the lines.
1020 lines.Insert( lines.Begin() + layoutParameters.startLineIndex,
1027 lines.Resize( numberOfLines );
1030 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--LayoutText\n\n" );
1035 void ReLayoutRightToLeftLines( const LayoutParameters& layoutParameters,
1036 CharacterIndex startIndex,
1037 Length numberOfCharacters,
1038 Vector<Vector2>& glyphPositions )
1040 const CharacterIndex lastCharacterIndex = startIndex + numberOfCharacters;
1042 // Traverses the paragraphs with right to left characters.
1043 for( LineIndex lineIndex = 0u; lineIndex < layoutParameters.numberOfBidirectionalInfoRuns; ++lineIndex )
1045 const BidirectionalLineInfoRun& bidiLine = *( layoutParameters.lineBidirectionalInfoRunsBuffer + lineIndex );
1047 if( startIndex >= bidiLine.characterRun.characterIndex + bidiLine.characterRun.numberOfCharacters )
1049 // Do not reorder the line if it has been already reordered.
1053 if( bidiLine.characterRun.characterIndex >= lastCharacterIndex )
1055 // Do not reorder the lines after the last requested character.
1059 const CharacterIndex characterVisualIndex = bidiLine.characterRun.characterIndex + *bidiLine.visualToLogicalMap;
1060 const GlyphInfo& glyph = *( layoutParameters.glyphsBuffer + *( layoutParameters.charactersToGlyphsBuffer + characterVisualIndex ) );
1062 float penX = ( 0.f > glyph.xBearing ) ? -glyph.xBearing : 0.f;
1064 Vector2* glyphPositionsBuffer = glyphPositions.Begin();
1066 // Traverses the characters of the right to left paragraph.
1067 for( CharacterIndex characterLogicalIndex = 0u;
1068 characterLogicalIndex < bidiLine.characterRun.numberOfCharacters;
1069 ++characterLogicalIndex )
1071 // Convert the character in the logical order into the character in the visual order.
1072 const CharacterIndex characterVisualIndex = bidiLine.characterRun.characterIndex + *( bidiLine.visualToLogicalMap + characterLogicalIndex );
1074 // Get the number of glyphs of the character.
1075 const Length numberOfGlyphs = *( layoutParameters.glyphsPerCharacterBuffer + characterVisualIndex );
1077 for( GlyphIndex index = 0u; index < numberOfGlyphs; ++index )
1079 // Convert the character in the visual order into the glyph in the visual order.
1080 const GlyphIndex glyphIndex = *( layoutParameters.charactersToGlyphsBuffer + characterVisualIndex ) + index;
1082 DALI_ASSERT_DEBUG( 0u <= glyphIndex && glyphIndex < layoutParameters.totalNumberOfGlyphs );
1084 const GlyphInfo& glyph = *( layoutParameters.glyphsBuffer + glyphIndex );
1085 Vector2& position = *( glyphPositionsBuffer + glyphIndex );
1087 position.x = penX + glyph.xBearing;
1088 penX += glyph.advance;
1094 void Align( const Size& size,
1095 CharacterIndex startIndex,
1096 Length numberOfCharacters,
1097 Vector<LineRun>& lines )
1099 const CharacterIndex lastCharacterPlusOne = startIndex + numberOfCharacters;
1101 // Traverse all lines and align the glyphs.
1102 for( Vector<LineRun>::Iterator it = lines.Begin(), endIt = lines.End();
1106 LineRun& line = *it;
1108 if( line.characterRun.characterIndex < startIndex )
1110 // Do not align lines which have already been aligned.
1114 if( line.characterRun.characterIndex >= lastCharacterPlusOne )
1116 // Do not align lines beyond the last laid-out character.
1120 // Calculate the line's alignment offset accordingly with the align option,
1121 // the box width, line length, and the paragraph's direction.
1122 CalculateHorizontalAlignment( size.width,
1127 void CalculateHorizontalAlignment( float boxWidth,
1130 line.alignmentOffset = 0.f;
1131 const bool isRTL = RTL == line.direction;
1132 float lineLength = line.width;
1134 HorizontalAlignment alignment = mHorizontalAlignment;
1137 // Swap the alignment type if the line is right to left.
1140 case HORIZONTAL_ALIGN_BEGIN:
1142 alignment = HORIZONTAL_ALIGN_END;
1145 case HORIZONTAL_ALIGN_CENTER:
1150 case HORIZONTAL_ALIGN_END:
1152 alignment = HORIZONTAL_ALIGN_BEGIN;
1158 // Calculate the horizontal line offset.
1161 case HORIZONTAL_ALIGN_BEGIN:
1163 line.alignmentOffset = 0.f;
1167 // 'Remove' the white spaces at the end of the line (which are at the beginning in visual order)
1168 line.alignmentOffset -= line.extraLength;
1172 case HORIZONTAL_ALIGN_CENTER:
1174 line.alignmentOffset = 0.5f * ( boxWidth - lineLength );
1178 line.alignmentOffset -= line.extraLength;
1181 line.alignmentOffset = floorf( line.alignmentOffset ); // try to avoid pixel alignment.
1184 case HORIZONTAL_ALIGN_END:
1188 lineLength += line.extraLength;
1191 line.alignmentOffset = boxWidth - lineLength;
1197 void Initialize( LineRun& line )
1199 line.glyphRun.glyphIndex = 0u;
1200 line.glyphRun.numberOfGlyphs = 0u;
1201 line.characterRun.characterIndex = 0u;
1202 line.characterRun.numberOfCharacters = 0u;
1204 line.ascender = 0.f;
1205 line.descender = 0.f;
1206 line.extraLength = 0.f;
1207 line.alignmentOffset = 0.f;
1208 line.direction = !RTL;
1209 line.ellipsis = false;
1212 LayoutEngine::Layout mLayout;
1213 LayoutEngine::HorizontalAlignment mHorizontalAlignment;
1214 LayoutEngine::VerticalAlignment mVerticalAlignment;
1217 IntrusivePtr<Metrics> mMetrics;
1219 bool mEllipsisEnabled:1;
1222 LayoutEngine::LayoutEngine()
1225 mImpl = new LayoutEngine::Impl();
1228 LayoutEngine::~LayoutEngine()
1233 void LayoutEngine::SetMetrics( MetricsPtr& metrics )
1235 mImpl->mMetrics = metrics;
1238 void LayoutEngine::SetLayout( Layout layout )
1240 mImpl->mLayout = layout;
1243 LayoutEngine::Layout LayoutEngine::GetLayout() const
1245 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "GetLayout[%d]\n", mImpl->mLayout);
1246 return mImpl->mLayout;
1249 void LayoutEngine::SetTextEllipsisEnabled( bool enabled )
1251 DALI_LOG_INFO( gLogFilter, Debug::General, "-->LayoutEngine::SetTextEllipsisEnabled[%s]\n", (enabled)?"true":"false" );
1252 mImpl->mEllipsisEnabled = enabled;
1255 bool LayoutEngine::GetTextEllipsisEnabled() const
1257 return mImpl->mEllipsisEnabled;
1260 void LayoutEngine::SetHorizontalAlignment( HorizontalAlignment alignment )
1262 mImpl->mHorizontalAlignment = alignment;
1265 LayoutEngine::HorizontalAlignment LayoutEngine::GetHorizontalAlignment() const
1267 return mImpl->mHorizontalAlignment;
1270 void LayoutEngine::SetVerticalAlignment( VerticalAlignment alignment )
1272 mImpl->mVerticalAlignment = alignment;
1275 LayoutEngine::VerticalAlignment LayoutEngine::GetVerticalAlignment() const
1277 return mImpl->mVerticalAlignment;
1280 void LayoutEngine::SetCursorWidth( int width )
1282 mImpl->mCursorWidth = static_cast<float>( width );
1285 int LayoutEngine::GetCursorWidth() const
1287 return static_cast<int>( mImpl->mCursorWidth );
1290 bool LayoutEngine::LayoutText( const LayoutParameters& layoutParameters,
1291 Vector<Vector2>& glyphPositions,
1292 Vector<LineRun>& lines,
1295 return mImpl->LayoutText( layoutParameters,
1301 void LayoutEngine::ReLayoutRightToLeftLines( const LayoutParameters& layoutParameters,
1302 CharacterIndex startIndex,
1303 Length numberOfCharacters,
1304 Vector<Vector2>& glyphPositions )
1306 mImpl->ReLayoutRightToLeftLines( layoutParameters,
1312 void LayoutEngine::Align( const Size& size,
1313 CharacterIndex startIndex,
1314 Length numberOfCharacters,
1315 Vector<LineRun>& lines )
1325 } // namespace Toolkit