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>
24 #include <dali/integration-api/debug.h>
25 #include <dali/devel-api/text-abstraction/font-client.h>
28 #include <dali-toolkit/internal/text/bidirectional-line-info-run.h>
29 #include <dali-toolkit/internal/text/cursor-helper-functions.h>
30 #include <dali-toolkit/internal/text/glyph-metrics-helper.h>
31 #include <dali-toolkit/internal/text/layouts/layout-parameters.h>
48 #if defined(DEBUG_ENABLED)
49 Debug::Filter* gLogFilter = Debug::Filter::New(Debug::Concise, true, "LOG_TEXT_LAYOUT");
52 const float MAX_FLOAT = std::numeric_limits<float>::max();
53 const bool RTL = true;
54 const float LINE_SPACING= 0.f;
56 inline bool isEmptyLineAtLast( const Vector<LineRun>& lines, const Vector<LineRun>::Iterator& line )
58 return ( (*line).characterRun.numberOfCharacters == 0 && line + 1u == lines.End() );
64 * @brief Stores temporary layout info of the line.
72 numberOfCharacters( 0u ),
73 ascender( -MAX_FLOAT ),
74 descender( MAX_FLOAT ),
77 previousAdvance( 0.f ),
79 wsLengthEndOfLine( 0.f )
90 numberOfCharacters = 0u;
92 descender = MAX_FLOAT;
95 GlyphIndex glyphIndex; ///< Index of the first glyph to be laid-out.
96 CharacterIndex characterIndex; ///< Index of the first character to be laid-out.
97 Length numberOfGlyphs; ///< The number of glyph which fit in one line.
98 Length numberOfCharacters; ///< The number of characters which fit in one line.
99 float ascender; ///< The maximum ascender of all fonts in the line.
100 float descender; ///< The minimum descender of all fonts in the line.
101 float lineSpacing; ///< The line spacing
102 float penX; ///< The origin of the current glyph ( is the start point plus the accumulation of all advances ).
103 float previousAdvance; ///< The advance of the previous glyph.
104 float length; ///< The current length of the line.
105 float wsLengthEndOfLine; ///< The length of the white spaces at the end of the line.
111 : mLayout( Layout::Engine::SINGLE_LINE_BOX ),
113 mDefaultLineSpacing( LINE_SPACING )
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;
161 lineLayout.penX = tmpLineLayout.penX;
162 lineLayout.previousAdvance = tmpLineLayout.previousAdvance;
164 lineLayout.length = tmpLineLayout.length;
165 lineLayout.wsLengthEndOfLine = tmpLineLayout.wsLengthEndOfLine;
167 // Sets the maximum ascender.
168 lineLayout.ascender = std::max( lineLayout.ascender, tmpLineLayout.ascender );
170 // Sets the minimum descender.
171 lineLayout.descender = std::min( lineLayout.descender, tmpLineLayout.descender );
175 * Retrieves the line layout for a given box width.
177 * @note This method lais out text as it were left to right. At this point is not possible to reorder the line
178 * because the number of characters of the line is not known (one of the responsabilities of this method
179 * is calculate that). Due to glyph's 'x' bearing, width and advance, when right to left or mixed right to left
180 * and left to right text is laid-out, it can be small differences in the line length. One solution is to
181 * reorder and re-lay out the text after this method and add or remove one extra glyph if needed. However,
182 * this method calculates which are the first and last glyphs of the line (the ones that causes the
183 * differences). This is a good point to check if there is problems with the text exceeding the boundaries
184 * of the control when there is right to left text.
186 * @param[in] parameters The layout parameters.
187 * @param[out] lineLayout The line layout.
188 * @param[in,out] paragraphDirection in: the current paragraph's direction, out: the next paragraph's direction. Is set after a must break.
189 * @param[in] completelyFill Whether to completely fill the line ( even if the last word exceeds the boundaries ).
191 void GetLineLayoutForBox( const Parameters& parameters,
192 LineLayout& lineLayout,
193 CharacterDirection& paragraphDirection,
194 bool completelyFill )
196 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "-->GetLineLayoutForBox\n" );
197 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " initial glyph index : %d\n", lineLayout.glyphIndex );
199 const bool isMultiline = mLayout == MULTI_LINE_BOX;
200 const bool isWordLaidOut = parameters.lineWrapMode == Text::LineWrap::WORD;
202 // The last glyph to be laid-out.
203 const GlyphIndex lastGlyphOfParagraphPlusOne = parameters.startGlyphIndex + parameters.numberOfGlyphs;
205 // If the first glyph has a negative bearing its absolute value needs to be added to the line length.
206 // In the case the line starts with a right to left character, if the width is longer than the advance,
207 // the difference needs to be added to the line length.
209 // Check whether the first glyph comes from a character that is shaped in multiple glyphs.
210 const Length numberOfGLyphsInGroup = GetNumberOfGlyphsOfGroup( lineLayout.glyphIndex,
211 lastGlyphOfParagraphPlusOne,
212 parameters.charactersPerGlyphBuffer );
214 GlyphMetrics glyphMetrics;
215 GetGlyphsMetrics( lineLayout.glyphIndex,
216 numberOfGLyphsInGroup,
218 parameters.glyphsBuffer,
221 // Set the direction of the first character of the line.
222 lineLayout.characterIndex = *( parameters.glyphsToCharactersBuffer + lineLayout.glyphIndex );
224 // Stores temporary line layout which has not been added to the final line layout.
225 LineLayout tmpLineLayout;
227 // Initialize the start point.
229 // The initial start point is zero. However it needs a correction according the 'x' bearing of the first glyph.
230 // i.e. if the bearing of the first glyph is negative it may exceed the boundaries of the text area.
231 // It needs to add as well space for the cursor if the text is in edit mode and extra space in case the text is outlined.
232 tmpLineLayout.penX = -glyphMetrics.xBearing + mCursorWidth + parameters.outlineWidth;
234 // Initialize the advance of the previous glyph.
235 tmpLineLayout.previousAdvance = 0.f;
237 // Calculate the line height if there is no characters.
238 FontId lastFontId = glyphMetrics.fontId;
239 UpdateLineHeight( glyphMetrics, tmpLineLayout );
241 bool oneWordLaidOut = false;
243 for( GlyphIndex glyphIndex = lineLayout.glyphIndex;
244 glyphIndex < lastGlyphOfParagraphPlusOne; )
246 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " glyph index : %d\n", glyphIndex );
248 // Check whether this glyph comes from a character that is shaped in multiple glyphs.
249 const Length numberOfGLyphsInGroup = GetNumberOfGlyphsOfGroup( glyphIndex,
250 lastGlyphOfParagraphPlusOne,
251 parameters.charactersPerGlyphBuffer );
253 GlyphMetrics glyphMetrics;
254 GetGlyphsMetrics( glyphIndex,
255 numberOfGLyphsInGroup,
257 parameters.glyphsBuffer,
260 const bool isLastGlyph = glyphIndex + numberOfGLyphsInGroup == parameters.totalNumberOfGlyphs;
262 // Check if the font of the current glyph is the same of the previous one.
263 // If it's different the ascender and descender need to be updated.
264 if( lastFontId != glyphMetrics.fontId )
266 UpdateLineHeight( glyphMetrics, tmpLineLayout );
267 lastFontId = glyphMetrics.fontId;
270 // Get the character indices for the current glyph. The last character index is needed
271 // because there are glyphs formed by more than one character but their break info is
272 // given only for the last character.
273 const Length charactersPerGlyph = *( parameters.charactersPerGlyphBuffer + glyphIndex + numberOfGLyphsInGroup - 1u );
274 const bool hasCharacters = charactersPerGlyph > 0u;
275 const CharacterIndex characterFirstIndex = *( parameters.glyphsToCharactersBuffer + glyphIndex );
276 const CharacterIndex characterLastIndex = characterFirstIndex + ( hasCharacters ? charactersPerGlyph - 1u : 0u );
278 // Get the line break info for the current character.
279 const LineBreakInfo lineBreakInfo = hasCharacters ? *( parameters.lineBreakInfoBuffer + characterLastIndex ) : TextAbstraction::LINE_NO_BREAK;
281 // Increase the number of characters.
282 tmpLineLayout.numberOfCharacters += charactersPerGlyph;
284 // Increase the number of glyphs.
285 tmpLineLayout.numberOfGlyphs += numberOfGLyphsInGroup;
287 // Check whether is a white space.
288 const Character character = *( parameters.textBuffer + characterFirstIndex );
289 const bool isWhiteSpace = TextAbstraction::IsWhiteSpace( character );
291 // Calculate the length of the line.
293 // Used to restore the temporal line layout when a single word does not fit in the control's width and is split by character.
294 const float previousTmpPenX = tmpLineLayout.penX;
295 const float previousTmpAdvance = tmpLineLayout.previousAdvance;
296 const float previousTmpLength = tmpLineLayout.length;
297 const float previousTmpWsLengthEndOfLine = tmpLineLayout.wsLengthEndOfLine;
301 // Add the length to the length of white spaces at the end of the line.
302 tmpLineLayout.wsLengthEndOfLine += glyphMetrics.advance; // The advance is used as the width is always zero for the white spaces.
306 tmpLineLayout.penX += tmpLineLayout.previousAdvance + tmpLineLayout.wsLengthEndOfLine;
307 tmpLineLayout.previousAdvance = ( glyphMetrics.advance + parameters.interGlyphExtraAdvance );
308 tmpLineLayout.length = tmpLineLayout.penX + glyphMetrics.xBearing + glyphMetrics.width;
310 // Clear the white space length at the end of the line.
311 tmpLineLayout.wsLengthEndOfLine = 0.f;
314 // Check if the accumulated length fits in the width of the box.
315 if( ( completelyFill || isMultiline ) && !isWhiteSpace &&
316 ( tmpLineLayout.length > parameters.boundingBox.width ) )
318 // Current word does not fit in the box's width.
319 if( !oneWordLaidOut || completelyFill )
321 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " Break the word by character\n" );
323 // The word doesn't fit in the control's width. It needs to be split by character.
324 if( tmpLineLayout.numberOfGlyphs > 0u )
326 tmpLineLayout.numberOfCharacters -= charactersPerGlyph;
327 tmpLineLayout.numberOfGlyphs -= numberOfGLyphsInGroup;
329 tmpLineLayout.penX = previousTmpPenX;
330 tmpLineLayout.previousAdvance = previousTmpAdvance;
331 tmpLineLayout.length = previousTmpLength;
332 tmpLineLayout.wsLengthEndOfLine = previousTmpWsLengthEndOfLine;
335 // Add part of the word to the line layout.
336 MergeLineLayout( lineLayout, tmpLineLayout );
340 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " Current word does not fit.\n" );
343 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--GetLineLayoutForBox.\n" );
348 if( ( isMultiline || isLastGlyph ) &&
349 ( TextAbstraction::LINE_MUST_BREAK == lineBreakInfo ) )
351 // Must break the line. Update the line layout and return.
352 MergeLineLayout( lineLayout, tmpLineLayout );
354 // Set the next paragraph's direction.
356 ( NULL != parameters.characterDirectionBuffer ) )
358 paragraphDirection = *( parameters.characterDirectionBuffer + 1u + characterLastIndex );
361 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " Must break\n" );
362 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--GetLineLayoutForBox\n" );
368 ( TextAbstraction::LINE_ALLOW_BREAK == lineBreakInfo ) )
370 oneWordLaidOut = isWordLaidOut;
371 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " One word laid-out\n" );
373 // Current glyph is the last one of the current word.
374 // Add the temporal layout to the current one.
375 MergeLineLayout( lineLayout, tmpLineLayout );
377 tmpLineLayout.Clear();
380 glyphIndex += numberOfGLyphsInGroup;
383 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--GetLineLayoutForBox\n" );
386 void SetGlyphPositions( const GlyphInfo* const glyphsBuffer,
387 Length numberOfGlyphs,
389 float interGlyphExtraAdvance,
390 Vector2* glyphPositionsBuffer )
392 // Traverse the glyphs and set the positions.
394 // Check if the x bearing of the first character is negative.
395 // If it has a negative x bearing, it will exceed the boundaries of the actor,
396 // so the penX position needs to be moved to the right.
398 const GlyphInfo& glyph = *glyphsBuffer;
399 float penX = -glyph.xBearing + mCursorWidth + outlineWidth;
401 for( GlyphIndex i = 0u; i < numberOfGlyphs; ++i )
403 const GlyphInfo& glyph = *( glyphsBuffer + i );
404 Vector2& position = *( glyphPositionsBuffer + i );
406 position.x = std::roundf( penX + glyph.xBearing );
407 position.y = -glyph.yBearing;
409 penX += ( glyph.advance + interGlyphExtraAdvance );
414 * @brief Resizes the line buffer.
416 * @param[in,out] lines The vector of lines. Used when the layout is created from scratch.
417 * @param[in,out] newLines The vector of lines used instead of @p lines when the layout is updated.
418 * @param[in,out] linesCapacity The capacity of the vector (either lines or newLines).
419 * @param[in] updateCurrentBuffer Whether the layout is updated.
421 * @return Pointer to either lines or newLines.
423 LineRun* ResizeLinesBuffer( Vector<LineRun>& lines,
424 Vector<LineRun>& newLines,
425 Length& linesCapacity,
426 bool updateCurrentBuffer )
428 LineRun* linesBuffer = NULL;
429 // Reserve more space for the next lines.
431 if( updateCurrentBuffer )
433 newLines.Resize( linesCapacity );
434 linesBuffer = newLines.Begin();
438 lines.Resize( linesCapacity );
439 linesBuffer = lines.Begin();
446 * Ellipsis a line if it exceeds the width's of the bounding box.
448 * @param[in] layoutParameters The parameters needed to layout the text.
449 * @param[in] layout The line layout.
450 * @param[in,out] layoutSize The text's layout size.
451 * @param[in,out] linesBuffer Pointer to the line's buffer.
452 * @param[in,out] glyphPositionsBuffer Pointer to the position's buffer.
453 * @param[in,out] numberOfLines The number of laid-out lines.
454 * @param[in] penY The vertical layout position.
455 * @param[in] currentParagraphDirection The current paragraph's direction.
456 * @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
458 * return Whether the line is ellipsized.
460 bool EllipsisLine( const Parameters& layoutParameters,
461 const LineLayout& layout,
463 LineRun* linesBuffer,
464 Vector2* glyphPositionsBuffer,
465 Length& numberOfLines,
467 CharacterDirection currentParagraphDirection,
468 bool& isAutoScrollEnabled )
470 const bool ellipsis = isAutoScrollEnabled ? ( penY - layout.descender > layoutParameters.boundingBox.height ) :
471 ( ( penY - layout.descender > layoutParameters.boundingBox.height ) ||
472 ( ( mLayout == SINGLE_LINE_BOX ) &&
473 ( layout.length > layoutParameters.boundingBox.width ) ) );
477 isAutoScrollEnabled = false;
478 // Do not layout more lines if ellipsis is enabled.
480 // The last line needs to be completely filled with characters.
481 // Part of a word may be used.
483 LineRun* lineRun = NULL;
484 LineLayout ellipsisLayout;
485 if( 0u != numberOfLines )
487 // Get the last line and layout it again with the 'completelyFill' flag to true.
488 lineRun = linesBuffer + ( numberOfLines - 1u );
490 penY -= layout.ascender - lineRun->descender + lineRun->lineSpacing;
492 ellipsisLayout.glyphIndex = lineRun->glyphRun.glyphIndex;
496 // At least there is space reserved for one line.
497 lineRun = linesBuffer;
499 lineRun->glyphRun.glyphIndex = 0u;
500 ellipsisLayout.glyphIndex = 0u;
505 GetLineLayoutForBox( layoutParameters,
507 currentParagraphDirection,
510 lineRun->glyphRun.numberOfGlyphs = ellipsisLayout.numberOfGlyphs;
511 lineRun->characterRun.characterIndex = ellipsisLayout.characterIndex;
512 lineRun->characterRun.numberOfCharacters = ellipsisLayout.numberOfCharacters;
513 lineRun->width = ellipsisLayout.length;
514 lineRun->extraLength = std::ceil( ellipsisLayout.wsLengthEndOfLine );
515 lineRun->ascender = ellipsisLayout.ascender;
516 lineRun->descender = ellipsisLayout.descender;
517 lineRun->direction = !RTL;
518 lineRun->ellipsis = true;
520 layoutSize.width = layoutParameters.boundingBox.width;
521 if( layoutSize.height < Math::MACHINE_EPSILON_1000 )
523 layoutSize.height += ( lineRun->ascender + -lineRun->descender ) + lineRun->lineSpacing;
526 SetGlyphPositions( layoutParameters.glyphsBuffer + lineRun->glyphRun.glyphIndex,
527 ellipsisLayout.numberOfGlyphs,
528 layoutParameters.outlineWidth,
529 layoutParameters.interGlyphExtraAdvance,
530 glyphPositionsBuffer + lineRun->glyphRun.glyphIndex - layoutParameters.startGlyphIndex );
537 * @brief Updates the text layout with a new laid-out line.
539 * @param[in] layoutParameters The parameters needed to layout the text.
540 * @param[in] layout The line layout.
541 * @param[in,out] layoutSize The text's layout size.
542 * @param[in,out] linesBuffer Pointer to the line's buffer.
543 * @param[in] index Index to the vector of glyphs.
544 * @param[in,out] numberOfLines The number of laid-out lines.
545 * @param[in] isLastLine Whether the laid-out line is the last one.
547 void UpdateTextLayout( const Parameters& layoutParameters,
548 const LineLayout& layout,
550 LineRun* linesBuffer,
552 Length& numberOfLines,
555 LineRun& lineRun = *( linesBuffer + numberOfLines );
558 lineRun.glyphRun.glyphIndex = index;
559 lineRun.glyphRun.numberOfGlyphs = layout.numberOfGlyphs;
560 lineRun.characterRun.characterIndex = layout.characterIndex;
561 lineRun.characterRun.numberOfCharacters = layout.numberOfCharacters;
562 lineRun.lineSpacing = mDefaultLineSpacing;
564 if( isLastLine && !layoutParameters.isLastNewParagraph )
566 const float width = layout.length + layout.wsLengthEndOfLine;
567 if( MULTI_LINE_BOX == mLayout )
569 lineRun.width = ( width > layoutParameters.boundingBox.width ) ? layoutParameters.boundingBox.width : width;
573 lineRun.width = width;
576 lineRun.extraLength = 0.f;
580 lineRun.width = layout.length;
581 lineRun.extraLength = std::ceil( layout.wsLengthEndOfLine );
584 // Rounds upward to avoid a non integer size.
585 lineRun.width = std::ceil( lineRun.width );
587 lineRun.ascender = layout.ascender;
588 lineRun.descender = layout.descender;
589 lineRun.direction = !RTL;
590 lineRun.ellipsis = false;
592 // Update the actual size.
593 if( lineRun.width > layoutSize.width )
595 layoutSize.width = lineRun.width;
598 layoutSize.height += ( lineRun.ascender + -lineRun.descender ) + lineRun.lineSpacing;
602 * @brief Updates the text layout with the last laid-out line.
604 * @param[in] layoutParameters The parameters needed to layout the text.
605 * @param[in] characterIndex The character index of the line.
606 * @param[in] glyphIndex The glyph index of the line.
607 * @param[in,out] layoutSize The text's layout size.
608 * @param[in,out] linesBuffer Pointer to the line's buffer.
609 * @param[in,out] numberOfLines The number of laid-out lines.
611 void UpdateTextLayout( const Parameters& layoutParameters,
612 CharacterIndex characterIndex,
613 GlyphIndex glyphIndex,
615 LineRun* linesBuffer,
616 Length& numberOfLines )
618 // Need to add a new line with no characters but with height to increase the layoutSize.height
619 const GlyphInfo& glyphInfo = *( layoutParameters.glyphsBuffer + layoutParameters.totalNumberOfGlyphs - 1u );
621 Text::FontMetrics fontMetrics;
622 if( 0u != glyphInfo.fontId )
624 mMetrics->GetFontMetrics( glyphInfo.fontId, fontMetrics );
627 LineRun& lineRun = *( linesBuffer + numberOfLines );
630 lineRun.glyphRun.glyphIndex = glyphIndex;
631 lineRun.glyphRun.numberOfGlyphs = 0u;
632 lineRun.characterRun.characterIndex = characterIndex;
633 lineRun.characterRun.numberOfCharacters = 0u;
635 lineRun.ascender = fontMetrics.ascender;
636 lineRun.descender = fontMetrics.descender;
637 lineRun.extraLength = 0.f;
638 lineRun.alignmentOffset = 0.f;
639 lineRun.direction = !RTL;
640 lineRun.ellipsis = false;
641 lineRun.lineSpacing = mDefaultLineSpacing;
643 layoutSize.height += ( lineRun.ascender + -lineRun.descender ) + lineRun.lineSpacing;
647 * @brief Updates the text's layout size adding the size of the previously laid-out lines.
649 * @param[in] lines The vector of lines (before the new laid-out lines are inserted).
650 * @param[in,out] layoutSize The text's layout size.
652 void UpdateLayoutSize( const Vector<LineRun>& lines,
655 for( Vector<LineRun>::ConstIterator it = lines.Begin(),
660 const LineRun& line = *it;
662 if( line.width > layoutSize.width )
664 layoutSize.width = line.width;
667 layoutSize.height += ( line.ascender + -line.descender ) + line.lineSpacing;
672 * @brief Updates the indices of the character and glyph runs of the lines before the new lines are inserted.
674 * @param[in] layoutParameters The parameters needed to layout the text.
675 * @param[in,out] lines The vector of lines (before the new laid-out lines are inserted).
676 * @param[in] characterOffset The offset to be added to the runs of characters.
677 * @param[in] glyphOffset The offset to be added to the runs of glyphs.
679 void UpdateLineIndexOffsets( const Parameters& layoutParameters,
680 Vector<LineRun>& lines,
681 Length characterOffset,
684 // Update the glyph and character runs.
685 for( Vector<LineRun>::Iterator it = lines.Begin() + layoutParameters.startLineIndex,
692 line.glyphRun.glyphIndex = glyphOffset;
693 line.characterRun.characterIndex = characterOffset;
695 glyphOffset += line.glyphRun.numberOfGlyphs;
696 characterOffset += line.characterRun.numberOfCharacters;
700 bool LayoutText( const Parameters& layoutParameters,
701 Vector<Vector2>& glyphPositions,
702 Vector<LineRun>& lines,
704 bool elideTextEnabled,
705 bool& isAutoScrollEnabled )
707 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "-->LayoutText\n" );
708 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " box size %f, %f\n", layoutParameters.boundingBox.width, layoutParameters.boundingBox.height );
710 if( 0u == layoutParameters.numberOfGlyphs )
712 // Add an extra line if the last character is a new paragraph character and the last line doesn't have zero characters.
713 if( layoutParameters.isLastNewParagraph )
715 Length numberOfLines = lines.Count();
716 if( 0u != numberOfLines )
718 const LineRun& lastLine = *( lines.End() - 1u );
720 if( 0u != lastLine.characterRun.numberOfCharacters )
722 // Need to add a new line with no characters but with height to increase the layoutSize.height
724 Initialize( newLine );
725 lines.PushBack( newLine );
727 UpdateTextLayout( layoutParameters,
728 lastLine.characterRun.characterIndex + lastLine.characterRun.numberOfCharacters,
729 lastLine.glyphRun.glyphIndex + lastLine.glyphRun.numberOfGlyphs,
737 // Calculates the layout size.
738 UpdateLayoutSize( lines,
741 // Rounds upward to avoid a non integer size.
742 layoutSize.height = std::ceil( layoutSize.height );
744 // Nothing else do if there are no glyphs to layout.
748 const GlyphIndex lastGlyphPlusOne = layoutParameters.startGlyphIndex + layoutParameters.numberOfGlyphs;
750 // In a previous layout, an extra line with no characters may have been added if the text ended with a new paragraph character.
751 // This extra line needs to be removed.
752 if( 0u != lines.Count() )
754 Vector<LineRun>::Iterator lastLine = lines.End() - 1u;
756 if( ( 0u == lastLine->characterRun.numberOfCharacters ) &&
757 ( lastGlyphPlusOne == layoutParameters.totalNumberOfGlyphs ) )
759 lines.Remove( lastLine );
763 // Set the first paragraph's direction.
764 CharacterDirection paragraphDirection = ( NULL != layoutParameters.characterDirectionBuffer ) ? *layoutParameters.characterDirectionBuffer : !RTL;
766 // Whether the layout is being updated or set from scratch.
767 const bool updateCurrentBuffer = layoutParameters.numberOfGlyphs < layoutParameters.totalNumberOfGlyphs;
769 Vector2* glyphPositionsBuffer = NULL;
770 Vector<Vector2> newGlyphPositions;
772 LineRun* linesBuffer = NULL;
773 Vector<LineRun> newLines;
775 // Estimate the number of lines.
776 Length linesCapacity = std::max( 1u, layoutParameters.estimatedNumberOfLines );
777 Length numberOfLines = 0u;
779 if( updateCurrentBuffer )
781 newGlyphPositions.Resize( layoutParameters.numberOfGlyphs );
782 glyphPositionsBuffer = newGlyphPositions.Begin();
784 newLines.Resize( linesCapacity );
785 linesBuffer = newLines.Begin();
789 glyphPositionsBuffer = glyphPositions.Begin();
791 lines.Resize( linesCapacity );
792 linesBuffer = lines.Begin();
795 float penY = CalculateLineOffset( lines,
796 layoutParameters.startLineIndex );
798 for( GlyphIndex index = layoutParameters.startGlyphIndex; index < lastGlyphPlusOne; )
800 CharacterDirection currentParagraphDirection = paragraphDirection;
802 // Get the layout for the line.
804 layout.glyphIndex = index;
805 GetLineLayoutForBox( layoutParameters,
810 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " glyph index %d\n", layout.glyphIndex );
811 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " character index %d\n", layout.characterIndex );
812 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " number of glyphs %d\n", layout.numberOfGlyphs );
813 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " number of characters %d\n", layout.numberOfCharacters );
814 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " length %f\n", layout.length );
816 if( 0u == layout.numberOfGlyphs )
818 // The width is too small and no characters are laid-out.
819 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--LayoutText width too small!\n\n" );
821 lines.Resize( numberOfLines );
823 // Rounds upward to avoid a non integer size.
824 layoutSize.height = std::ceil( layoutSize.height );
829 // Set the line position. Discard if ellipsis is enabled and the position exceeds the boundaries
831 penY += layout.ascender;
833 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " pen y %f\n", penY );
835 bool ellipsis = false;
836 if( elideTextEnabled )
838 // Does the ellipsis of the last line.
839 ellipsis = EllipsisLine( layoutParameters,
843 glyphPositionsBuffer,
846 currentParagraphDirection,
847 isAutoScrollEnabled );
852 // No more lines to layout.
857 // Whether the last line has been laid-out.
858 const bool isLastLine = index + layout.numberOfGlyphs == layoutParameters.totalNumberOfGlyphs;
860 if( numberOfLines == linesCapacity )
862 // Reserve more space for the next lines.
863 linesBuffer = ResizeLinesBuffer( lines,
866 updateCurrentBuffer );
869 // Updates the current text's layout with the line's layout.
870 UpdateTextLayout( layoutParameters,
878 const GlyphIndex nextIndex = index + layout.numberOfGlyphs;
880 if( ( nextIndex == layoutParameters.totalNumberOfGlyphs ) &&
881 layoutParameters.isLastNewParagraph &&
882 ( mLayout == MULTI_LINE_BOX ) )
884 // The last character of the text is a new paragraph character.
885 // An extra line with no characters is added to increase the text's height
886 // in order to place the cursor.
888 if( numberOfLines == linesCapacity )
890 // Reserve more space for the next lines.
891 linesBuffer = ResizeLinesBuffer( lines,
894 updateCurrentBuffer );
897 UpdateTextLayout( layoutParameters,
898 layout.characterIndex + layout.numberOfCharacters,
899 index + layout.numberOfGlyphs,
903 } // whether to add a last line.
905 // Sets the positions of the glyphs.
906 SetGlyphPositions( layoutParameters.glyphsBuffer + index,
907 layout.numberOfGlyphs,
908 layoutParameters.outlineWidth,
909 layoutParameters.interGlyphExtraAdvance,
910 glyphPositionsBuffer + index - layoutParameters.startGlyphIndex );
912 // Updates the vertical pen's position.
913 penY += -layout.descender + layout.lineSpacing + mDefaultLineSpacing;
915 // Increase the glyph index.
918 } // end for() traversing glyphs.
920 if( updateCurrentBuffer )
922 glyphPositions.Insert( glyphPositions.Begin() + layoutParameters.startGlyphIndex,
923 newGlyphPositions.Begin(),
924 newGlyphPositions.End() );
925 glyphPositions.Resize( layoutParameters.totalNumberOfGlyphs );
927 newLines.Resize( numberOfLines );
929 // Current text's layout size adds only the newly laid-out lines.
930 // Updates the layout size with the previously laid-out lines.
931 UpdateLayoutSize( lines,
934 if( 0u != newLines.Count() )
936 const LineRun& lastLine = *( newLines.End() - 1u );
938 const Length characterOffset = lastLine.characterRun.characterIndex + lastLine.characterRun.numberOfCharacters;
939 const Length glyphOffset = lastLine.glyphRun.glyphIndex + lastLine.glyphRun.numberOfGlyphs;
941 // Update the indices of the runs before the new laid-out lines are inserted.
942 UpdateLineIndexOffsets( layoutParameters,
948 lines.Insert( lines.Begin() + layoutParameters.startLineIndex,
955 lines.Resize( numberOfLines );
958 // Rounds upward to avoid a non integer size.
959 layoutSize.height = std::ceil( layoutSize.height );
961 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--LayoutText\n\n" );
966 void ReLayoutRightToLeftLines( const Parameters& layoutParameters,
967 CharacterIndex startIndex,
968 Length numberOfCharacters,
969 Vector<Vector2>& glyphPositions )
971 const CharacterIndex lastCharacterIndex = startIndex + numberOfCharacters;
973 // Traverses the paragraphs with right to left characters.
974 for( LineIndex lineIndex = 0u; lineIndex < layoutParameters.numberOfBidirectionalInfoRuns; ++lineIndex )
976 const BidirectionalLineInfoRun& bidiLine = *( layoutParameters.lineBidirectionalInfoRunsBuffer + lineIndex );
978 if( startIndex >= bidiLine.characterRun.characterIndex + bidiLine.characterRun.numberOfCharacters )
980 // Do not reorder the line if it has been already reordered.
984 if( bidiLine.characterRun.characterIndex >= lastCharacterIndex )
986 // Do not reorder the lines after the last requested character.
990 const CharacterIndex characterVisualIndex = bidiLine.characterRun.characterIndex + *bidiLine.visualToLogicalMap;
991 const GlyphInfo& glyph = *( layoutParameters.glyphsBuffer + *( layoutParameters.charactersToGlyphsBuffer + characterVisualIndex ) );
993 float penX = -glyph.xBearing + layoutParameters.outlineWidth + mCursorWidth;
995 Vector2* glyphPositionsBuffer = glyphPositions.Begin();
997 // Traverses the characters of the right to left paragraph.
998 for( CharacterIndex characterLogicalIndex = 0u;
999 characterLogicalIndex < bidiLine.characterRun.numberOfCharacters;
1000 ++characterLogicalIndex )
1002 // Convert the character in the logical order into the character in the visual order.
1003 const CharacterIndex characterVisualIndex = bidiLine.characterRun.characterIndex + *( bidiLine.visualToLogicalMap + characterLogicalIndex );
1005 // Get the number of glyphs of the character.
1006 const Length numberOfGlyphs = *( layoutParameters.glyphsPerCharacterBuffer + characterVisualIndex );
1008 for( GlyphIndex index = 0u; index < numberOfGlyphs; ++index )
1010 // Convert the character in the visual order into the glyph in the visual order.
1011 const GlyphIndex glyphIndex = *( layoutParameters.charactersToGlyphsBuffer + characterVisualIndex ) + index;
1013 DALI_ASSERT_DEBUG( 0u <= glyphIndex && glyphIndex < layoutParameters.totalNumberOfGlyphs );
1015 const GlyphInfo& glyph = *( layoutParameters.glyphsBuffer + glyphIndex );
1016 Vector2& position = *( glyphPositionsBuffer + glyphIndex );
1018 position.x = std::round( penX + glyph.xBearing );
1019 penX += ( glyph.advance + layoutParameters.interGlyphExtraAdvance );
1025 void Align( const Size& size,
1026 CharacterIndex startIndex,
1027 Length numberOfCharacters,
1028 Text::HorizontalAlignment::Type horizontalAlignment,
1029 Vector<LineRun>& lines,
1030 float& alignmentOffset,
1031 Dali::LayoutDirection::Type layoutDirection,
1032 bool matchSystemLanguageDirection )
1034 const CharacterIndex lastCharacterPlusOne = startIndex + numberOfCharacters;
1036 alignmentOffset = MAX_FLOAT;
1037 // Traverse all lines and align the glyphs.
1038 for( Vector<LineRun>::Iterator it = lines.Begin(), endIt = lines.End();
1042 LineRun& line = *it;
1044 if( line.characterRun.characterIndex < startIndex )
1046 // Do not align lines which have already been aligned.
1050 if( line.characterRun.characterIndex > lastCharacterPlusOne )
1052 // Do not align lines beyond the last laid-out character.
1056 if( line.characterRun.characterIndex == lastCharacterPlusOne && !isEmptyLineAtLast( lines, it ) )
1058 // Do not align lines beyond the last laid-out character unless the line is last and empty.
1062 // Calculate the line's alignment offset accordingly with the align option,
1063 // the box width, line length, and the paragraph's direction.
1064 CalculateHorizontalAlignment( size.width,
1065 horizontalAlignment,
1068 matchSystemLanguageDirection );
1070 // Updates the alignment offset.
1071 alignmentOffset = std::min( alignmentOffset, line.alignmentOffset );
1075 void CalculateHorizontalAlignment( float boxWidth,
1076 HorizontalAlignment::Type horizontalAlignment,
1078 Dali::LayoutDirection::Type layoutDirection,
1079 bool matchSystemLanguageDirection )
1081 line.alignmentOffset = 0.f;
1082 const bool isLineRTL = RTL == line.direction;
1083 // Whether to swap the alignment.
1084 // 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.
1085 bool isLayoutRTL = isLineRTL;
1086 float lineLength = line.width;
1088 // match align for system language direction
1089 if( matchSystemLanguageDirection )
1091 // Swap the alignment type if the line is right to left.
1092 isLayoutRTL = layoutDirection == LayoutDirection::RIGHT_TO_LEFT;
1094 // Calculate the horizontal line offset.
1095 switch( horizontalAlignment )
1097 case HorizontalAlignment::BEGIN:
1103 lineLength += line.extraLength;
1106 line.alignmentOffset = boxWidth - lineLength;
1110 line.alignmentOffset = 0.f;
1114 // 'Remove' the white spaces at the end of the line (which are at the beginning in visual order)
1115 line.alignmentOffset -= line.extraLength;
1120 case HorizontalAlignment::CENTER:
1122 line.alignmentOffset = 0.5f * ( boxWidth - lineLength );
1126 line.alignmentOffset -= line.extraLength;
1129 line.alignmentOffset = std::floor( line.alignmentOffset ); // floor() avoids pixel alignment issues.
1132 case HorizontalAlignment::END:
1136 line.alignmentOffset = 0.f;
1140 // 'Remove' the white spaces at the end of the line (which are at the beginning in visual order)
1141 line.alignmentOffset -= line.extraLength;
1148 lineLength += line.extraLength;
1151 line.alignmentOffset = boxWidth - lineLength;
1158 void Initialize( LineRun& line )
1160 line.glyphRun.glyphIndex = 0u;
1161 line.glyphRun.numberOfGlyphs = 0u;
1162 line.characterRun.characterIndex = 0u;
1163 line.characterRun.numberOfCharacters = 0u;
1165 line.ascender = 0.f;
1166 line.descender = 0.f;
1167 line.extraLength = 0.f;
1168 line.alignmentOffset = 0.f;
1169 line.direction = !RTL;
1170 line.ellipsis = false;
1171 line.lineSpacing = mDefaultLineSpacing;
1176 float mDefaultLineSpacing;
1178 IntrusivePtr<Metrics> mMetrics;
1184 mImpl = new Engine::Impl();
1192 void Engine::SetMetrics( MetricsPtr& metrics )
1194 mImpl->mMetrics = metrics;
1197 void Engine::SetLayout( Type layout )
1199 mImpl->mLayout = layout;
1202 Engine::Type Engine::GetLayout() const
1204 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "GetLayout[%d]\n", mImpl->mLayout);
1205 return mImpl->mLayout;
1208 void Engine::SetCursorWidth( int width )
1210 mImpl->mCursorWidth = static_cast<float>( width );
1213 int Engine::GetCursorWidth() const
1215 return static_cast<int>( mImpl->mCursorWidth );
1218 bool Engine::LayoutText( const Parameters& layoutParameters,
1219 Vector<Vector2>& glyphPositions,
1220 Vector<LineRun>& lines,
1222 bool elideTextEnabled,
1223 bool& isAutoScrollEnabled )
1225 return mImpl->LayoutText( layoutParameters,
1230 isAutoScrollEnabled );
1233 void Engine::ReLayoutRightToLeftLines( const Parameters& layoutParameters,
1234 CharacterIndex startIndex,
1235 Length numberOfCharacters,
1236 Vector<Vector2>& glyphPositions )
1238 mImpl->ReLayoutRightToLeftLines( layoutParameters,
1244 void Engine::Align( const Size& size,
1245 CharacterIndex startIndex,
1246 Length numberOfCharacters,
1247 Text::HorizontalAlignment::Type horizontalAlignment,
1248 Vector<LineRun>& lines,
1249 float& alignmentOffset,
1250 Dali::LayoutDirection::Type layoutDirection,
1251 bool matchSystemLanguageDirection )
1256 horizontalAlignment,
1260 matchSystemLanguageDirection );
1263 void Engine::SetDefaultLineSpacing( float lineSpacing )
1265 mImpl->mDefaultLineSpacing = lineSpacing;
1268 float Engine::GetDefaultLineSpacing() const
1270 return mImpl->mDefaultLineSpacing;
1273 } // namespace Layout
1277 } // namespace Toolkit