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;
59 * @brief Stores temporary layout info of the line.
67 numberOfCharacters( 0u ),
68 ascender( -MAX_FLOAT ),
69 descender( MAX_FLOAT ),
72 previousAdvance( 0.f ),
74 wsLengthEndOfLine( 0.f )
85 numberOfCharacters = 0u;
87 descender = MAX_FLOAT;
90 GlyphIndex glyphIndex; ///< Index of the first glyph to be laid-out.
91 CharacterIndex characterIndex; ///< Index of the first character to be laid-out.
92 Length numberOfGlyphs; ///< The number of glyph which fit in one line.
93 Length numberOfCharacters; ///< The number of characters which fit in one line.
94 float ascender; ///< The maximum ascender of all fonts in the line.
95 float descender; ///< The minimum descender of all fonts in the line.
96 float lineSpacing; ///< The line spacing
97 float penX; ///< The origin of the current glyph ( is the start point plus the accumulation of all advances ).
98 float previousAdvance; ///< The advance of the previous glyph.
99 float length; ///< The current length of the line.
100 float wsLengthEndOfLine; ///< The length of the white spaces at the end of the line.
106 : mLayout( Layout::Engine::SINGLE_LINE_BOX ),
108 mDefaultLineSpacing( LINE_SPACING )
113 * @brief Updates the line ascender and descender with the metrics of a new font.
115 * @param[in] glyphMetrics The metrics of the new font.
116 * @param[in,out] lineLayout The line layout.
118 void UpdateLineHeight( const GlyphMetrics& glyphMetrics, LineLayout& lineLayout )
120 Text::FontMetrics fontMetrics;
121 if( 0u != glyphMetrics.fontId )
123 mMetrics->GetFontMetrics( glyphMetrics.fontId, fontMetrics );
127 fontMetrics.ascender = glyphMetrics.fontHeight;
128 fontMetrics.descender = 0.f;
129 fontMetrics.height = fontMetrics.ascender;
130 fontMetrics.underlinePosition = 0.f;
131 fontMetrics.underlineThickness = 1.f;
134 // Sets the maximum ascender.
135 lineLayout.ascender = std::max( lineLayout.ascender, fontMetrics.ascender );
137 // Sets the minimum descender.
138 lineLayout.descender = std::min( lineLayout.descender, fontMetrics.descender );
140 // set the line spacing
141 lineLayout.lineSpacing = mDefaultLineSpacing;
145 * @brief Merges a temporary line layout into the line layout.
147 * @param[in,out] lineLayout The line layout.
148 * @param[in] tmpLineLayout A temporary line layout.
150 void MergeLineLayout( LineLayout& lineLayout,
151 const LineLayout& tmpLineLayout )
153 lineLayout.numberOfCharacters += tmpLineLayout.numberOfCharacters;
154 lineLayout.numberOfGlyphs += tmpLineLayout.numberOfGlyphs;
156 lineLayout.penX = tmpLineLayout.penX;
157 lineLayout.previousAdvance = tmpLineLayout.previousAdvance;
159 lineLayout.length = tmpLineLayout.length;
160 lineLayout.wsLengthEndOfLine = tmpLineLayout.wsLengthEndOfLine;
162 // Sets the maximum ascender.
163 lineLayout.ascender = std::max( lineLayout.ascender, tmpLineLayout.ascender );
165 // Sets the minimum descender.
166 lineLayout.descender = std::min( lineLayout.descender, tmpLineLayout.descender );
170 * Retrieves the line layout for a given box width.
172 * @note This method lais out text as it were left to right. At this point is not possible to reorder the line
173 * because the number of characters of the line is not known (one of the responsabilities of this method
174 * is calculate that). Due to glyph's 'x' bearing, width and advance, when right to left or mixed right to left
175 * and left to right text is laid-out, it can be small differences in the line length. One solution is to
176 * reorder and re-lay out the text after this method and add or remove one extra glyph if needed. However,
177 * this method calculates which are the first and last glyphs of the line (the ones that causes the
178 * differences). This is a good point to check if there is problems with the text exceeding the boundaries
179 * of the control when there is right to left text.
181 * @param[in] parameters The layout parameters.
182 * @param[out] lineLayout The line layout.
183 * @param[in,out] paragraphDirection in: the current paragraph's direction, out: the next paragraph's direction. Is set after a must break.
184 * @param[in] completelyFill Whether to completely fill the line ( even if the last word exceeds the boundaries ).
186 void GetLineLayoutForBox( const Parameters& parameters,
187 LineLayout& lineLayout,
188 CharacterDirection& paragraphDirection,
189 bool completelyFill )
191 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "-->GetLineLayoutForBox\n" );
192 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " initial glyph index : %d\n", lineLayout.glyphIndex );
194 const bool isMultiline = mLayout == MULTI_LINE_BOX;
195 const bool isWordLaidOut = parameters.lineWrapMode == Text::LineWrap::WORD;
197 // The last glyph to be laid-out.
198 const GlyphIndex lastGlyphOfParagraphPlusOne = parameters.startGlyphIndex + parameters.numberOfGlyphs;
200 // If the first glyph has a negative bearing its absolute value needs to be added to the line length.
201 // In the case the line starts with a right to left character, if the width is longer than the advance,
202 // the difference needs to be added to the line length.
204 // Check whether the first glyph comes from a character that is shaped in multiple glyphs.
205 const Length numberOfGLyphsInGroup = GetNumberOfGlyphsOfGroup( lineLayout.glyphIndex,
206 lastGlyphOfParagraphPlusOne,
207 parameters.charactersPerGlyphBuffer );
209 GlyphMetrics glyphMetrics;
210 GetGlyphsMetrics( lineLayout.glyphIndex,
211 numberOfGLyphsInGroup,
213 parameters.glyphsBuffer,
216 // Set the direction of the first character of the line.
217 lineLayout.characterIndex = *( parameters.glyphsToCharactersBuffer + lineLayout.glyphIndex );
219 // Stores temporary line layout which has not been added to the final line layout.
220 LineLayout tmpLineLayout;
222 // Initialize the start point.
224 // The initial start point is zero. However it needs a correction according the 'x' bearing of the first glyph.
225 // i.e. if the bearing of the first glyph is negative it may exceed the boundaries of the text area.
226 // 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.
227 tmpLineLayout.penX = ( 0.f > glyphMetrics.xBearing ) ? -glyphMetrics.xBearing : 0.f + mCursorWidth + parameters.outlineWidth;
229 // Initialize the advance of the previous glyph.
230 tmpLineLayout.previousAdvance = 0.f;
232 // Calculate the line height if there is no characters.
233 FontId lastFontId = glyphMetrics.fontId;
234 UpdateLineHeight( glyphMetrics, tmpLineLayout );
236 bool oneWordLaidOut = false;
238 for( GlyphIndex glyphIndex = lineLayout.glyphIndex;
239 glyphIndex < lastGlyphOfParagraphPlusOne; )
241 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " glyph index : %d\n", glyphIndex );
243 // Check whether this glyph comes from a character that is shaped in multiple glyphs.
244 const Length numberOfGLyphsInGroup = GetNumberOfGlyphsOfGroup( glyphIndex,
245 lastGlyphOfParagraphPlusOne,
246 parameters.charactersPerGlyphBuffer );
248 GlyphMetrics glyphMetrics;
249 GetGlyphsMetrics( glyphIndex,
250 numberOfGLyphsInGroup,
252 parameters.glyphsBuffer,
255 const bool isLastGlyph = glyphIndex + numberOfGLyphsInGroup == parameters.totalNumberOfGlyphs;
257 // Check if the font of the current glyph is the same of the previous one.
258 // If it's different the ascender and descender need to be updated.
259 if( lastFontId != glyphMetrics.fontId )
261 UpdateLineHeight( glyphMetrics, tmpLineLayout );
262 lastFontId = glyphMetrics.fontId;
265 // Get the character indices for the current glyph. The last character index is needed
266 // because there are glyphs formed by more than one character but their break info is
267 // given only for the last character.
268 const Length charactersPerGlyph = *( parameters.charactersPerGlyphBuffer + glyphIndex + numberOfGLyphsInGroup - 1u );
269 const bool hasCharacters = charactersPerGlyph > 0u;
270 const CharacterIndex characterFirstIndex = *( parameters.glyphsToCharactersBuffer + glyphIndex );
271 const CharacterIndex characterLastIndex = characterFirstIndex + ( hasCharacters ? charactersPerGlyph - 1u : 0u );
273 // Get the line break info for the current character.
274 const LineBreakInfo lineBreakInfo = hasCharacters ? *( parameters.lineBreakInfoBuffer + characterLastIndex ) : TextAbstraction::LINE_NO_BREAK;
276 // Increase the number of characters.
277 tmpLineLayout.numberOfCharacters += charactersPerGlyph;
279 // Increase the number of glyphs.
280 tmpLineLayout.numberOfGlyphs += numberOfGLyphsInGroup;
282 // Check whether is a white space.
283 const Character character = *( parameters.textBuffer + characterFirstIndex );
284 const bool isWhiteSpace = TextAbstraction::IsWhiteSpace( character );
286 // Calculate the length of the line.
288 // Used to restore the temporal line layout when a single word does not fit in the control's width and is split by character.
289 const float previousTmpPenX = tmpLineLayout.penX;
290 const float previousTmpAdvance = tmpLineLayout.previousAdvance;
291 const float previousTmpLength = tmpLineLayout.length;
292 const float previousTmpWsLengthEndOfLine = tmpLineLayout.wsLengthEndOfLine;
296 // Add the length to the length of white spaces at the end of the line.
297 tmpLineLayout.wsLengthEndOfLine += glyphMetrics.advance; // The advance is used as the width is always zero for the white spaces.
301 tmpLineLayout.previousAdvance = ( glyphMetrics.advance + parameters.interGlyphExtraAdvance );
302 tmpLineLayout.penX += tmpLineLayout.previousAdvance + tmpLineLayout.wsLengthEndOfLine;
303 tmpLineLayout.length = tmpLineLayout.penX;
305 // Clear the white space length at the end of the line.
306 tmpLineLayout.wsLengthEndOfLine = 0.f;
309 // Check if the accumulated length fits in the width of the box.
310 if( ( completelyFill || isMultiline ) && !isWhiteSpace &&
311 ( tmpLineLayout.length > parameters.boundingBox.width ) )
313 // Current word does not fit in the box's width.
314 if( !oneWordLaidOut || completelyFill )
316 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " Break the word by character\n" );
318 // The word doesn't fit in the control's width. It needs to be split by character.
319 if( tmpLineLayout.numberOfGlyphs > 0u )
321 tmpLineLayout.numberOfCharacters -= charactersPerGlyph;
322 tmpLineLayout.numberOfGlyphs -= numberOfGLyphsInGroup;
324 tmpLineLayout.penX = previousTmpPenX;
325 tmpLineLayout.previousAdvance = previousTmpAdvance;
326 tmpLineLayout.length = previousTmpLength;
327 tmpLineLayout.wsLengthEndOfLine = previousTmpWsLengthEndOfLine;
330 // Add part of the word to the line layout.
331 MergeLineLayout( lineLayout, tmpLineLayout );
335 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " Current word does not fit.\n" );
338 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--GetLineLayoutForBox.\n" );
343 if( ( isMultiline || isLastGlyph ) &&
344 ( TextAbstraction::LINE_MUST_BREAK == lineBreakInfo ) )
346 // Must break the line. Update the line layout and return.
347 MergeLineLayout( lineLayout, tmpLineLayout );
349 // Set the next paragraph's direction.
351 ( NULL != parameters.characterDirectionBuffer ) )
353 paragraphDirection = *( parameters.characterDirectionBuffer + 1u + characterLastIndex );
356 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " Must break\n" );
357 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--GetLineLayoutForBox\n" );
363 ( TextAbstraction::LINE_ALLOW_BREAK == lineBreakInfo ) )
365 oneWordLaidOut = isWordLaidOut;
366 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " One word laid-out\n" );
368 // Current glyph is the last one of the current word.
369 // Add the temporal layout to the current one.
370 MergeLineLayout( lineLayout, tmpLineLayout );
372 tmpLineLayout.Clear();
375 glyphIndex += numberOfGLyphsInGroup;
378 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--GetLineLayoutForBox\n" );
381 void SetGlyphPositions( const GlyphInfo* const glyphsBuffer,
382 Length numberOfGlyphs,
384 float interGlyphExtraAdvance,
385 Vector2* glyphPositionsBuffer )
387 // Traverse the glyphs and set the positions.
389 // Check if the x bearing of the first character is negative.
390 // If it has a negative x bearing, it will exceed the boundaries of the actor,
391 // so the penX position needs to be moved to the right.
393 const GlyphInfo& glyph = *glyphsBuffer;
394 float penX = ( 0.f > glyph.xBearing ) ? -glyph.xBearing : 0.f + mCursorWidth + outlineWidth;
396 for( GlyphIndex i = 0u; i < numberOfGlyphs; ++i )
398 const GlyphInfo& glyph = *( glyphsBuffer + i );
399 Vector2& position = *( glyphPositionsBuffer + i );
401 position.x = std::roundf( penX + glyph.xBearing );
402 position.y = -glyph.yBearing;
404 penX += ( glyph.advance + interGlyphExtraAdvance );
409 * @brief Resizes the line buffer.
411 * @param[in,out] lines The vector of lines. Used when the layout is created from scratch.
412 * @param[in,out] newLines The vector of lines used instead of @p lines when the layout is updated.
413 * @param[in,out] linesCapacity The capacity of the vector (either lines or newLines).
414 * @param[in] updateCurrentBuffer Whether the layout is updated.
416 * @return Pointer to either lines or newLines.
418 LineRun* ResizeLinesBuffer( Vector<LineRun>& lines,
419 Vector<LineRun>& newLines,
420 Length& linesCapacity,
421 bool updateCurrentBuffer )
423 LineRun* linesBuffer = NULL;
424 // Reserve more space for the next lines.
426 if( updateCurrentBuffer )
428 newLines.Resize( linesCapacity );
429 linesBuffer = newLines.Begin();
433 lines.Resize( linesCapacity );
434 linesBuffer = lines.Begin();
441 * Ellipsis a line if it exceeds the width's of the bounding box.
443 * @param[in] layoutParameters The parameters needed to layout the text.
444 * @param[in] layout The line layout.
445 * @param[in,out] layoutSize The text's layout size.
446 * @param[in,out] linesBuffer Pointer to the line's buffer.
447 * @param[in,out] glyphPositionsBuffer Pointer to the position's buffer.
448 * @param[in,out] numberOfLines The number of laid-out lines.
449 * @param[in] penY The vertical layout position.
450 * @param[in] currentParagraphDirection The current paragraph's direction.
451 * @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
453 * return Whether the line is ellipsized.
455 bool EllipsisLine( const Parameters& layoutParameters,
456 const LineLayout& layout,
458 LineRun* linesBuffer,
459 Vector2* glyphPositionsBuffer,
460 Length& numberOfLines,
462 CharacterDirection currentParagraphDirection,
463 bool& isAutoScrollEnabled )
465 const bool ellipsis = isAutoScrollEnabled ? ( penY - layout.descender > layoutParameters.boundingBox.height ) :
466 ( ( penY - layout.descender > layoutParameters.boundingBox.height ) ||
467 ( ( mLayout == SINGLE_LINE_BOX ) &&
468 ( layout.length > layoutParameters.boundingBox.width ) ) );
472 isAutoScrollEnabled = false;
473 // Do not layout more lines if ellipsis is enabled.
475 // The last line needs to be completely filled with characters.
476 // Part of a word may be used.
478 LineRun* lineRun = NULL;
479 LineLayout ellipsisLayout;
480 if( 0u != numberOfLines )
482 // Get the last line and layout it again with the 'completelyFill' flag to true.
483 lineRun = linesBuffer + ( numberOfLines - 1u );
485 penY -= layout.ascender - lineRun->descender + lineRun->lineSpacing;
487 ellipsisLayout.glyphIndex = lineRun->glyphRun.glyphIndex;
491 // At least there is space reserved for one line.
492 lineRun = linesBuffer;
494 lineRun->glyphRun.glyphIndex = 0u;
495 ellipsisLayout.glyphIndex = 0u;
500 GetLineLayoutForBox( layoutParameters,
502 currentParagraphDirection,
505 lineRun->glyphRun.numberOfGlyphs = ellipsisLayout.numberOfGlyphs;
506 lineRun->characterRun.characterIndex = ellipsisLayout.characterIndex;
507 lineRun->characterRun.numberOfCharacters = ellipsisLayout.numberOfCharacters;
508 lineRun->width = ellipsisLayout.length;
509 lineRun->extraLength = std::ceil( ellipsisLayout.wsLengthEndOfLine );
510 lineRun->ascender = ellipsisLayout.ascender;
511 lineRun->descender = ellipsisLayout.descender;
512 lineRun->direction = !RTL;
513 lineRun->ellipsis = true;
515 layoutSize.width = layoutParameters.boundingBox.width;
516 if( layoutSize.height < Math::MACHINE_EPSILON_1000 )
518 layoutSize.height += ( lineRun->ascender + -lineRun->descender ) + lineRun->lineSpacing;
521 SetGlyphPositions( layoutParameters.glyphsBuffer + lineRun->glyphRun.glyphIndex,
522 ellipsisLayout.numberOfGlyphs,
523 layoutParameters.outlineWidth,
524 layoutParameters.interGlyphExtraAdvance,
525 glyphPositionsBuffer + lineRun->glyphRun.glyphIndex - layoutParameters.startGlyphIndex );
532 * @brief Updates the text layout with a new laid-out line.
534 * @param[in] layoutParameters The parameters needed to layout the text.
535 * @param[in] layout The line layout.
536 * @param[in,out] layoutSize The text's layout size.
537 * @param[in,out] linesBuffer Pointer to the line's buffer.
538 * @param[in] index Index to the vector of glyphs.
539 * @param[in,out] numberOfLines The number of laid-out lines.
540 * @param[in] isLastLine Whether the laid-out line is the last one.
542 void UpdateTextLayout( const Parameters& layoutParameters,
543 const LineLayout& layout,
545 LineRun* linesBuffer,
547 Length& numberOfLines,
550 LineRun& lineRun = *( linesBuffer + numberOfLines );
553 lineRun.glyphRun.glyphIndex = index;
554 lineRun.glyphRun.numberOfGlyphs = layout.numberOfGlyphs;
555 lineRun.characterRun.characterIndex = layout.characterIndex;
556 lineRun.characterRun.numberOfCharacters = layout.numberOfCharacters;
557 lineRun.lineSpacing = mDefaultLineSpacing;
559 if( isLastLine && !layoutParameters.isLastNewParagraph )
561 const float width = layout.length + layout.wsLengthEndOfLine;
562 if( MULTI_LINE_BOX == mLayout )
564 lineRun.width = ( width > layoutParameters.boundingBox.width ) ? layoutParameters.boundingBox.width : width;
568 lineRun.width = width;
571 lineRun.extraLength = 0.f;
575 lineRun.width = layout.length;
576 lineRun.extraLength = std::ceil( layout.wsLengthEndOfLine );
579 // Rounds upward to avoid a non integer size.
580 lineRun.width = std::ceil( lineRun.width );
582 lineRun.ascender = layout.ascender;
583 lineRun.descender = layout.descender;
584 lineRun.direction = !RTL;
585 lineRun.ellipsis = false;
587 // Update the actual size.
588 if( lineRun.width > layoutSize.width )
590 layoutSize.width = lineRun.width;
593 layoutSize.height += ( lineRun.ascender + -lineRun.descender ) + lineRun.lineSpacing;
597 * @brief Updates the text layout with the last laid-out line.
599 * @param[in] layoutParameters The parameters needed to layout the text.
600 * @param[in] characterIndex The character index of the line.
601 * @param[in] glyphIndex The glyph index of the line.
602 * @param[in,out] layoutSize The text's layout size.
603 * @param[in,out] linesBuffer Pointer to the line's buffer.
604 * @param[in,out] numberOfLines The number of laid-out lines.
606 void UpdateTextLayout( const Parameters& layoutParameters,
607 CharacterIndex characterIndex,
608 GlyphIndex glyphIndex,
610 LineRun* linesBuffer,
611 Length& numberOfLines )
613 // Need to add a new line with no characters but with height to increase the layoutSize.height
614 const GlyphInfo& glyphInfo = *( layoutParameters.glyphsBuffer + layoutParameters.totalNumberOfGlyphs - 1u );
616 Text::FontMetrics fontMetrics;
617 if( 0u != glyphInfo.fontId )
619 mMetrics->GetFontMetrics( glyphInfo.fontId, fontMetrics );
622 LineRun& lineRun = *( linesBuffer + numberOfLines );
625 lineRun.glyphRun.glyphIndex = glyphIndex;
626 lineRun.glyphRun.numberOfGlyphs = 0u;
627 lineRun.characterRun.characterIndex = characterIndex;
628 lineRun.characterRun.numberOfCharacters = 0u;
630 lineRun.ascender = fontMetrics.ascender;
631 lineRun.descender = fontMetrics.descender;
632 lineRun.extraLength = 0.f;
633 lineRun.alignmentOffset = 0.f;
634 lineRun.direction = !RTL;
635 lineRun.ellipsis = false;
636 lineRun.lineSpacing = mDefaultLineSpacing;
638 layoutSize.height += ( lineRun.ascender + -lineRun.descender ) + lineRun.lineSpacing;
642 * @brief Updates the text's layout size adding the size of the previously laid-out lines.
644 * @param[in] lines The vector of lines (before the new laid-out lines are inserted).
645 * @param[in,out] layoutSize The text's layout size.
647 void UpdateLayoutSize( const Vector<LineRun>& lines,
650 for( Vector<LineRun>::ConstIterator it = lines.Begin(),
655 const LineRun& line = *it;
657 if( line.width > layoutSize.width )
659 layoutSize.width = line.width;
662 layoutSize.height += ( line.ascender + -line.descender ) + line.lineSpacing;
667 * @brief Updates the indices of the character and glyph runs of the lines before the new lines are inserted.
669 * @param[in] layoutParameters The parameters needed to layout the text.
670 * @param[in,out] lines The vector of lines (before the new laid-out lines are inserted).
671 * @param[in] characterOffset The offset to be added to the runs of characters.
672 * @param[in] glyphOffset The offset to be added to the runs of glyphs.
674 void UpdateLineIndexOffsets( const Parameters& layoutParameters,
675 Vector<LineRun>& lines,
676 Length characterOffset,
679 // Update the glyph and character runs.
680 for( Vector<LineRun>::Iterator it = lines.Begin() + layoutParameters.startLineIndex,
687 line.glyphRun.glyphIndex = glyphOffset;
688 line.characterRun.characterIndex = characterOffset;
690 glyphOffset += line.glyphRun.numberOfGlyphs;
691 characterOffset += line.characterRun.numberOfCharacters;
695 bool LayoutText( const Parameters& layoutParameters,
696 Vector<Vector2>& glyphPositions,
697 Vector<LineRun>& lines,
699 bool elideTextEnabled,
700 bool& isAutoScrollEnabled )
702 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "-->LayoutText\n" );
703 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " box size %f, %f\n", layoutParameters.boundingBox.width, layoutParameters.boundingBox.height );
705 if( 0u == layoutParameters.numberOfGlyphs )
707 // Add an extra line if the last character is a new paragraph character and the last line doesn't have zero characters.
708 if( layoutParameters.isLastNewParagraph )
710 Length numberOfLines = lines.Count();
711 if( 0u != numberOfLines )
713 const LineRun& lastLine = *( lines.End() - 1u );
715 if( 0u != lastLine.characterRun.numberOfCharacters )
717 // Need to add a new line with no characters but with height to increase the layoutSize.height
719 Initialize( newLine );
720 lines.PushBack( newLine );
722 UpdateTextLayout( layoutParameters,
723 lastLine.characterRun.characterIndex + lastLine.characterRun.numberOfCharacters,
724 lastLine.glyphRun.glyphIndex + lastLine.glyphRun.numberOfGlyphs,
732 // Calculates the layout size.
733 UpdateLayoutSize( lines,
736 // Rounds upward to avoid a non integer size.
737 layoutSize.height = std::ceil( layoutSize.height );
739 // Nothing else do if there are no glyphs to layout.
743 const GlyphIndex lastGlyphPlusOne = layoutParameters.startGlyphIndex + layoutParameters.numberOfGlyphs;
745 // In a previous layout, an extra line with no characters may have been added if the text ended with a new paragraph character.
746 // This extra line needs to be removed.
747 if( 0u != lines.Count() )
749 Vector<LineRun>::Iterator lastLine = lines.End() - 1u;
751 if( ( 0u == lastLine->characterRun.numberOfCharacters ) &&
752 ( lastGlyphPlusOne == layoutParameters.totalNumberOfGlyphs ) )
754 lines.Remove( lastLine );
758 // Set the first paragraph's direction.
759 CharacterDirection paragraphDirection = ( NULL != layoutParameters.characterDirectionBuffer ) ? *layoutParameters.characterDirectionBuffer : !RTL;
761 // Whether the layout is being updated or set from scratch.
762 const bool updateCurrentBuffer = layoutParameters.numberOfGlyphs < layoutParameters.totalNumberOfGlyphs;
764 Vector2* glyphPositionsBuffer = NULL;
765 Vector<Vector2> newGlyphPositions;
767 LineRun* linesBuffer = NULL;
768 Vector<LineRun> newLines;
770 // Estimate the number of lines.
771 Length linesCapacity = std::max( 1u, layoutParameters.estimatedNumberOfLines );
772 Length numberOfLines = 0u;
774 if( updateCurrentBuffer )
776 newGlyphPositions.Resize( layoutParameters.numberOfGlyphs );
777 glyphPositionsBuffer = newGlyphPositions.Begin();
779 newLines.Resize( linesCapacity );
780 linesBuffer = newLines.Begin();
784 glyphPositionsBuffer = glyphPositions.Begin();
786 lines.Resize( linesCapacity );
787 linesBuffer = lines.Begin();
790 float penY = CalculateLineOffset( lines,
791 layoutParameters.startLineIndex );
793 for( GlyphIndex index = layoutParameters.startGlyphIndex; index < lastGlyphPlusOne; )
795 CharacterDirection currentParagraphDirection = paragraphDirection;
797 // Get the layout for the line.
799 layout.glyphIndex = index;
800 GetLineLayoutForBox( layoutParameters,
805 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " glyph index %d\n", layout.glyphIndex );
806 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " character index %d\n", layout.characterIndex );
807 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " number of glyphs %d\n", layout.numberOfGlyphs );
808 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " number of characters %d\n", layout.numberOfCharacters );
809 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " length %f\n", layout.length );
811 if( 0u == layout.numberOfGlyphs )
813 // The width is too small and no characters are laid-out.
814 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--LayoutText width too small!\n\n" );
816 lines.Resize( numberOfLines );
818 // Rounds upward to avoid a non integer size.
819 layoutSize.height = std::ceil( layoutSize.height );
824 // Set the line position. Discard if ellipsis is enabled and the position exceeds the boundaries
826 penY += layout.ascender;
828 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " pen y %f\n", penY );
830 bool ellipsis = false;
831 if( elideTextEnabled )
833 // Does the ellipsis of the last line.
834 ellipsis = EllipsisLine( layoutParameters,
838 glyphPositionsBuffer,
841 currentParagraphDirection,
842 isAutoScrollEnabled );
847 // No more lines to layout.
852 // Whether the last line has been laid-out.
853 const bool isLastLine = index + layout.numberOfGlyphs == layoutParameters.totalNumberOfGlyphs;
855 if( numberOfLines == linesCapacity )
857 // Reserve more space for the next lines.
858 linesBuffer = ResizeLinesBuffer( lines,
861 updateCurrentBuffer );
864 // Updates the current text's layout with the line's layout.
865 UpdateTextLayout( layoutParameters,
873 const GlyphIndex nextIndex = index + layout.numberOfGlyphs;
875 if( ( nextIndex == layoutParameters.totalNumberOfGlyphs ) &&
876 layoutParameters.isLastNewParagraph &&
877 ( mLayout == MULTI_LINE_BOX ) )
879 // The last character of the text is a new paragraph character.
880 // An extra line with no characters is added to increase the text's height
881 // in order to place the cursor.
883 if( numberOfLines == linesCapacity )
885 // Reserve more space for the next lines.
886 linesBuffer = ResizeLinesBuffer( lines,
889 updateCurrentBuffer );
892 UpdateTextLayout( layoutParameters,
893 layout.characterIndex + layout.numberOfCharacters,
894 index + layout.numberOfGlyphs,
898 } // whether to add a last line.
900 // Sets the positions of the glyphs.
901 SetGlyphPositions( layoutParameters.glyphsBuffer + index,
902 layout.numberOfGlyphs,
903 layoutParameters.outlineWidth,
904 layoutParameters.interGlyphExtraAdvance,
905 glyphPositionsBuffer + index - layoutParameters.startGlyphIndex );
907 // Updates the vertical pen's position.
908 penY += -layout.descender + layout.lineSpacing + mDefaultLineSpacing;
910 // Increase the glyph index.
913 } // end for() traversing glyphs.
915 if( updateCurrentBuffer )
917 glyphPositions.Insert( glyphPositions.Begin() + layoutParameters.startGlyphIndex,
918 newGlyphPositions.Begin(),
919 newGlyphPositions.End() );
920 glyphPositions.Resize( layoutParameters.totalNumberOfGlyphs );
922 newLines.Resize( numberOfLines );
924 // Current text's layout size adds only the newly laid-out lines.
925 // Updates the layout size with the previously laid-out lines.
926 UpdateLayoutSize( lines,
929 if( 0u != newLines.Count() )
931 const LineRun& lastLine = *( newLines.End() - 1u );
933 const Length characterOffset = lastLine.characterRun.characterIndex + lastLine.characterRun.numberOfCharacters;
934 const Length glyphOffset = lastLine.glyphRun.glyphIndex + lastLine.glyphRun.numberOfGlyphs;
936 // Update the indices of the runs before the new laid-out lines are inserted.
937 UpdateLineIndexOffsets( layoutParameters,
943 lines.Insert( lines.Begin() + layoutParameters.startLineIndex,
950 lines.Resize( numberOfLines );
953 // Rounds upward to avoid a non integer size.
954 layoutSize.height = std::ceil( layoutSize.height );
956 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--LayoutText\n\n" );
961 void ReLayoutRightToLeftLines( const Parameters& layoutParameters,
962 CharacterIndex startIndex,
963 Length numberOfCharacters,
964 Vector<Vector2>& glyphPositions )
966 const CharacterIndex lastCharacterIndex = startIndex + numberOfCharacters;
968 // Traverses the paragraphs with right to left characters.
969 for( LineIndex lineIndex = 0u; lineIndex < layoutParameters.numberOfBidirectionalInfoRuns; ++lineIndex )
971 const BidirectionalLineInfoRun& bidiLine = *( layoutParameters.lineBidirectionalInfoRunsBuffer + lineIndex );
973 if( startIndex >= bidiLine.characterRun.characterIndex + bidiLine.characterRun.numberOfCharacters )
975 // Do not reorder the line if it has been already reordered.
979 if( bidiLine.characterRun.characterIndex >= lastCharacterIndex )
981 // Do not reorder the lines after the last requested character.
985 const CharacterIndex characterVisualIndex = bidiLine.characterRun.characterIndex + *bidiLine.visualToLogicalMap;
986 const GlyphInfo& glyph = *( layoutParameters.glyphsBuffer + *( layoutParameters.charactersToGlyphsBuffer + characterVisualIndex ) );
988 float penX = ( 0.f > glyph.xBearing ) ? -glyph.xBearing : 0.f + layoutParameters.outlineWidth + mCursorWidth;
990 Vector2* glyphPositionsBuffer = glyphPositions.Begin();
992 // Traverses the characters of the right to left paragraph.
993 for( CharacterIndex characterLogicalIndex = 0u;
994 characterLogicalIndex < bidiLine.characterRun.numberOfCharacters;
995 ++characterLogicalIndex )
997 // Convert the character in the logical order into the character in the visual order.
998 const CharacterIndex characterVisualIndex = bidiLine.characterRun.characterIndex + *( bidiLine.visualToLogicalMap + characterLogicalIndex );
1000 // Get the number of glyphs of the character.
1001 const Length numberOfGlyphs = *( layoutParameters.glyphsPerCharacterBuffer + characterVisualIndex );
1003 for( GlyphIndex index = 0u; index < numberOfGlyphs; ++index )
1005 // Convert the character in the visual order into the glyph in the visual order.
1006 const GlyphIndex glyphIndex = *( layoutParameters.charactersToGlyphsBuffer + characterVisualIndex ) + index;
1008 DALI_ASSERT_DEBUG( 0u <= glyphIndex && glyphIndex < layoutParameters.totalNumberOfGlyphs );
1010 const GlyphInfo& glyph = *( layoutParameters.glyphsBuffer + glyphIndex );
1011 Vector2& position = *( glyphPositionsBuffer + glyphIndex );
1013 position.x = std::round( penX + glyph.xBearing );
1014 penX += ( glyph.advance + layoutParameters.interGlyphExtraAdvance );
1020 void Align( const Size& size,
1021 CharacterIndex startIndex,
1022 Length numberOfCharacters,
1023 Text::HorizontalAlignment::Type horizontalAlignment,
1024 Vector<LineRun>& lines,
1025 float& alignmentOffset,
1026 Dali::LayoutDirection::Type layoutDirection,
1027 bool matchSystemLanguageDirection )
1029 const CharacterIndex lastCharacterPlusOne = startIndex + numberOfCharacters;
1031 alignmentOffset = MAX_FLOAT;
1032 // Traverse all lines and align the glyphs.
1033 for( Vector<LineRun>::Iterator it = lines.Begin(), endIt = lines.End();
1037 LineRun& line = *it;
1039 if( line.characterRun.characterIndex < startIndex )
1041 // Do not align lines which have already been aligned.
1045 if( line.characterRun.characterIndex >= lastCharacterPlusOne )
1047 // Do not align lines beyond the last laid-out character.
1051 // Calculate the line's alignment offset accordingly with the align option,
1052 // the box width, line length, and the paragraph's direction.
1053 CalculateHorizontalAlignment( size.width,
1054 horizontalAlignment,
1057 matchSystemLanguageDirection );
1059 // Updates the alignment offset.
1060 alignmentOffset = std::min( alignmentOffset, line.alignmentOffset );
1064 void CalculateHorizontalAlignment( float boxWidth,
1065 HorizontalAlignment::Type horizontalAlignment,
1067 Dali::LayoutDirection::Type layoutDirection,
1068 bool matchSystemLanguageDirection )
1070 line.alignmentOffset = 0.f;
1071 const bool isLineRTL = RTL == line.direction;
1072 // Whether to swap the alignment.
1073 // 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.
1074 bool isLayoutRTL = isLineRTL;
1075 float lineLength = line.width;
1077 // match align for system language direction
1078 if( matchSystemLanguageDirection )
1080 // Swap the alignment type if the line is right to left.
1081 isLayoutRTL = layoutDirection == LayoutDirection::RIGHT_TO_LEFT;
1083 // Calculate the horizontal line offset.
1084 switch( horizontalAlignment )
1086 case HorizontalAlignment::BEGIN:
1092 lineLength += line.extraLength;
1095 line.alignmentOffset = boxWidth - lineLength;
1099 line.alignmentOffset = 0.f;
1103 // 'Remove' the white spaces at the end of the line (which are at the beginning in visual order)
1104 line.alignmentOffset -= line.extraLength;
1109 case HorizontalAlignment::CENTER:
1111 line.alignmentOffset = 0.5f * ( boxWidth - lineLength );
1115 line.alignmentOffset -= line.extraLength;
1118 line.alignmentOffset = std::floor( line.alignmentOffset ); // floor() avoids pixel alignment issues.
1121 case HorizontalAlignment::END:
1125 line.alignmentOffset = 0.f;
1129 // 'Remove' the white spaces at the end of the line (which are at the beginning in visual order)
1130 line.alignmentOffset -= line.extraLength;
1137 lineLength += line.extraLength;
1140 line.alignmentOffset = boxWidth - lineLength;
1147 void Initialize( LineRun& line )
1149 line.glyphRun.glyphIndex = 0u;
1150 line.glyphRun.numberOfGlyphs = 0u;
1151 line.characterRun.characterIndex = 0u;
1152 line.characterRun.numberOfCharacters = 0u;
1154 line.ascender = 0.f;
1155 line.descender = 0.f;
1156 line.extraLength = 0.f;
1157 line.alignmentOffset = 0.f;
1158 line.direction = !RTL;
1159 line.ellipsis = false;
1160 line.lineSpacing = mDefaultLineSpacing;
1165 float mDefaultLineSpacing;
1167 IntrusivePtr<Metrics> mMetrics;
1173 mImpl = new Engine::Impl();
1181 void Engine::SetMetrics( MetricsPtr& metrics )
1183 mImpl->mMetrics = metrics;
1186 void Engine::SetLayout( Type layout )
1188 mImpl->mLayout = layout;
1191 Engine::Type Engine::GetLayout() const
1193 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "GetLayout[%d]\n", mImpl->mLayout);
1194 return mImpl->mLayout;
1197 void Engine::SetCursorWidth( int width )
1199 mImpl->mCursorWidth = static_cast<float>( width );
1202 int Engine::GetCursorWidth() const
1204 return static_cast<int>( mImpl->mCursorWidth );
1207 bool Engine::LayoutText( const Parameters& layoutParameters,
1208 Vector<Vector2>& glyphPositions,
1209 Vector<LineRun>& lines,
1211 bool elideTextEnabled,
1212 bool& isAutoScrollEnabled )
1214 return mImpl->LayoutText( layoutParameters,
1219 isAutoScrollEnabled );
1222 void Engine::ReLayoutRightToLeftLines( const Parameters& layoutParameters,
1223 CharacterIndex startIndex,
1224 Length numberOfCharacters,
1225 Vector<Vector2>& glyphPositions )
1227 mImpl->ReLayoutRightToLeftLines( layoutParameters,
1233 void Engine::Align( const Size& size,
1234 CharacterIndex startIndex,
1235 Length numberOfCharacters,
1236 Text::HorizontalAlignment::Type horizontalAlignment,
1237 Vector<LineRun>& lines,
1238 float& alignmentOffset,
1239 Dali::LayoutDirection::Type layoutDirection,
1240 bool matchSystemLanguageDirection )
1245 horizontalAlignment,
1249 matchSystemLanguageDirection );
1252 void Engine::SetDefaultLineSpacing( float lineSpacing )
1254 mImpl->mDefaultLineSpacing = lineSpacing;
1257 float Engine::GetDefaultLineSpacing() const
1259 return mImpl->mDefaultLineSpacing;
1262 } // namespace Layout
1266 } // namespace Toolkit