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/public-api/math/vector2.h>
24 #include <dali/devel-api/text-abstraction/font-client.h>
25 #include <dali/integration-api/debug.h>
28 #include <dali-toolkit/internal/text/layouts/layout-parameters.h>
29 #include <dali-toolkit/internal/text/bidirectional-line-info-run.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;
53 * @brief Stores temporary layout info of the line.
61 numberOfCharacters( 0u ),
63 widthAdvanceDiff( 0.f ),
64 wsLengthEndOfLine( 0.f ),
66 descender( MAX_FLOAT )
77 numberOfCharacters = 0u;
79 widthAdvanceDiff = 0.f;
80 wsLengthEndOfLine = 0.f;
82 descender = MAX_FLOAT;
85 GlyphIndex glyphIndex; ///< Index of the first glyph to be laid-out.
86 CharacterIndex characterIndex; ///< Index of the first character to be laid-out.
87 Length numberOfGlyphs; ///< The number of glyph which fit in one line.
88 Length numberOfCharacters; ///< The number of characters which fit in one line.
89 float length; ///< The length of the glyphs which fit in one line.
90 float widthAdvanceDiff; ///< The difference between the xBearing + width and the advance of the last glyph.
91 float wsLengthEndOfLine; ///< The length of the white spaces at the end of the line.
92 float ascender; ///< The maximum ascender of all fonts in the line.
93 float descender; ///< The minimum descender of all fonts in the line.
96 struct LayoutEngine::Impl
99 : mLayout( LayoutEngine::SINGLE_LINE_BOX ),
100 mHorizontalAlignment( LayoutEngine::HORIZONTAL_ALIGN_BEGIN ),
101 mVerticalAlignment( LayoutEngine::VERTICAL_ALIGN_TOP ),
102 mEllipsisEnabled( false )
104 mFontClient = TextAbstraction::FontClient::Get();
108 * @brief Updates the line ascender and descender with the metrics of a new font.
110 * @param[in] fontId The id of the new font.
111 * @param[in,out] lineLayout The line layout.
113 void UpdateLineHeight( FontId fontId, LineLayout& lineLayout )
115 Text::FontMetrics fontMetrics;
116 mFontClient.GetFontMetrics( fontId, fontMetrics );
118 // Sets the maximum ascender.
119 if( fontMetrics.ascender > lineLayout.ascender )
121 lineLayout.ascender = fontMetrics.ascender;
124 // Sets the minimum descender.
125 if( fontMetrics.descender < lineLayout.descender )
127 lineLayout.descender = fontMetrics.descender;
132 * @brief Merges a temporary line layout into the line layout.
134 * @param[in,out] lineLayout The line layout.
135 * @param[in] tmpLineLayout A temporary line layout.
137 void MergeLineLayout( LineLayout& lineLayout,
138 const LineLayout& tmpLineLayout )
140 lineLayout.numberOfCharacters += tmpLineLayout.numberOfCharacters;
141 lineLayout.numberOfGlyphs += tmpLineLayout.numberOfGlyphs;
142 lineLayout.length += tmpLineLayout.length;
144 if( 0.f < tmpLineLayout.length )
146 lineLayout.length += lineLayout.wsLengthEndOfLine;
148 lineLayout.wsLengthEndOfLine = tmpLineLayout.wsLengthEndOfLine;
149 lineLayout.widthAdvanceDiff = tmpLineLayout.widthAdvanceDiff;
153 lineLayout.wsLengthEndOfLine += tmpLineLayout.wsLengthEndOfLine;
156 if( tmpLineLayout.ascender > lineLayout.ascender )
158 lineLayout.ascender = tmpLineLayout.ascender;
161 if( tmpLineLayout.descender < lineLayout.descender )
163 lineLayout.descender = tmpLineLayout.descender;
168 * Retrieves the line layout for a given box width.
170 * @param[in] parameters The layout parameters.
171 * @param[out] lineLayout The line layout.
172 * @param[in] completelyFill Whether to completely fill the line ( even if the last word exceeds the boundaries ).
174 void GetLineLayoutForBox( const LayoutParameters& parameters,
175 LineLayout& lineLayout,
176 bool completelyFill )
178 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "-->GetLineLayoutForBox\n" );
179 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " initial glyph index : %d\n", lineLayout.glyphIndex );
180 // Stores temporary line layout which has not been added to the final line layout.
181 LineLayout tmpLineLayout;
183 const bool isMultiline = mLayout == MULTI_LINE_BOX;
184 const GlyphIndex lastGlyphIndex = parameters.totalNumberOfGlyphs - 1u;
186 // If the first glyph has a negative bearing its absolute value needs to be added to the line length.
187 // In the case the line starts with a right to left character the bearing needs to be substracted to the line length.
188 const GlyphInfo& glyphInfo = *( parameters.glyphsBuffer + lineLayout.glyphIndex );
189 float initialHorizontalBearing = glyphInfo.xBearing;
191 lineLayout.characterIndex = *( parameters.glyphsToCharactersBuffer + lineLayout.glyphIndex );
192 const CharacterDirection firstCharacterDirection = ( NULL == parameters.characterDirectionBuffer ) ? false : *( parameters.characterDirectionBuffer + lineLayout.characterIndex );
194 if( RTL == firstCharacterDirection )
196 initialHorizontalBearing = -initialHorizontalBearing;
198 if( 0.f < glyphInfo.xBearing )
200 tmpLineLayout.length = glyphInfo.xBearing;
201 initialHorizontalBearing = 0.f;
206 if( 0.f > glyphInfo.xBearing )
208 tmpLineLayout.length = -glyphInfo.xBearing;
209 initialHorizontalBearing = 0.f;
213 // Calculate the line height if there is no characters.
214 FontId lastFontId = glyphInfo.fontId;
215 UpdateLineHeight( lastFontId, tmpLineLayout );
217 const float boundingBoxWidth = parameters.boundingBox.width - initialHorizontalBearing;
219 bool oneWordLaidOut = false;
221 for( GlyphIndex glyphIndex = lineLayout.glyphIndex;
222 glyphIndex < parameters.totalNumberOfGlyphs;
225 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " glyph index : %d\n", glyphIndex );
226 const bool isLastGlyph = glyphIndex == lastGlyphIndex;
228 // Get the glyph info.
229 const GlyphInfo& glyphInfo = *( parameters.glyphsBuffer + glyphIndex );
231 // Get the character indices for the current glyph. The last character index is needed
232 // because there are glyphs formed by more than one character but their break info is
233 // given only for the last character.
234 const Length charactersPerGlyph = *( parameters.charactersPerGlyphBuffer + glyphIndex );
235 const CharacterIndex characterFirstIndex = *( parameters.glyphsToCharactersBuffer + glyphIndex );
236 const CharacterIndex characterLastIndex = characterFirstIndex + ( ( 1u > charactersPerGlyph ) ? 0u : charactersPerGlyph - 1u );
238 // Get the line break info for the current character.
239 const LineBreakInfo lineBreakInfo = *( parameters.lineBreakInfoBuffer + characterLastIndex );
241 // Get the word break info for the current character.
242 const WordBreakInfo wordBreakInfo = *( parameters.wordBreakInfoBuffer + characterLastIndex );
244 // Increase the number of characters.
245 tmpLineLayout.numberOfCharacters += charactersPerGlyph;
247 // Increase the number of glyphs.
248 tmpLineLayout.numberOfGlyphs++;
250 // Check whether is a white space.
251 const Character character = *( parameters.textBuffer + characterFirstIndex );
252 const bool isWhiteSpace = TextAbstraction::IsWhiteSpace( character );
254 // Used to restore the temporal line layout when a single word does not fit in the control's width and is split by character.
255 const float previousTmpLineLength = tmpLineLayout.length;
256 const float previousTmpWidthAdvanceDiff = tmpLineLayout.widthAdvanceDiff;
258 // Increase the accumulated length.
261 // Add the length to the length of white spaces at the end of the line.
262 tmpLineLayout.wsLengthEndOfLine += glyphInfo.advance; // The advance is used as the width is always zero for the white spaces.
266 // Add as well any previous white space length.
267 tmpLineLayout.length += tmpLineLayout.wsLengthEndOfLine + glyphInfo.advance;
268 if( RTL == firstCharacterDirection )
270 tmpLineLayout.widthAdvanceDiff = -glyphInfo.xBearing;
274 tmpLineLayout.widthAdvanceDiff = glyphInfo.xBearing + glyphInfo.width - glyphInfo.advance;
277 // Clear the white space length at the end of the line.
278 tmpLineLayout.wsLengthEndOfLine = 0.f;
281 // Check if the accumulated length fits in the width of the box.
282 if( ( completelyFill || isMultiline ) && !isWhiteSpace &&
283 ( lineLayout.length + lineLayout.wsLengthEndOfLine + tmpLineLayout.length + tmpLineLayout.widthAdvanceDiff > boundingBoxWidth ) )
285 // Current word does not fit in the box's width.
286 if( !oneWordLaidOut || completelyFill )
288 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " Break the word by character\n" );
290 // The word's with doesn't fit in the control's with. It needs to be split by character.
291 if( tmpLineLayout.numberOfGlyphs > 0u )
293 tmpLineLayout.numberOfCharacters -= charactersPerGlyph;
294 --tmpLineLayout.numberOfGlyphs;
295 tmpLineLayout.length = previousTmpLineLength;
296 tmpLineLayout.widthAdvanceDiff = previousTmpWidthAdvanceDiff;
299 // Add part of the word to the line layout.
300 MergeLineLayout( lineLayout, tmpLineLayout );
304 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " Current word does not fit.\n" );
306 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--GetLineLayoutForBox\n" );
310 if( ( isMultiline || isLastGlyph ) &&
311 ( TextAbstraction::LINE_MUST_BREAK == lineBreakInfo ) )
313 // Must break the line. Update the line layout and return.
314 MergeLineLayout( lineLayout, tmpLineLayout );
316 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " Must break\n" );
317 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--GetLineLayoutForBox\n" );
322 ( TextAbstraction::WORD_BREAK == wordBreakInfo ) )
324 oneWordLaidOut = true;
325 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " One word laid out\n" );
327 // Current glyph is the last one of the current word.
328 // Add the temporal layout to the current one.
329 MergeLineLayout( lineLayout, tmpLineLayout );
331 tmpLineLayout.Clear();
334 // Check if the font of the current glyph is the same of the previous one.
335 // If it's different the ascender and descender need to be updated.
336 if( lastFontId != glyphInfo.fontId )
338 UpdateLineHeight( glyphInfo.fontId, tmpLineLayout );
339 lastFontId = glyphInfo.fontId;
342 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--GetLineLayoutForBox\n" );
345 void SetGlyphPositions( const GlyphInfo* const glyphsBuffer,
346 Length numberOfGlyphs,
348 Vector2* glyphPositionsBuffer )
350 // Traverse the glyphs and set the positions.
352 // Check if the x bearing of the first character is negative.
353 // If it has a negative x bearing, it will exceed the boundaries of the actor,
354 // so the penX position needs to be moved to the right.
357 const GlyphInfo& glyph = *glyphsBuffer;
358 if( 0.f > glyph.xBearing )
360 penX = -glyph.xBearing;
363 for( GlyphIndex i = 0u; i < numberOfGlyphs; ++i )
365 const GlyphInfo& glyph = *( glyphsBuffer + i );
366 Vector2& position = *( glyphPositionsBuffer + i );
368 position.x = penX + glyph.xBearing;
369 position.y = penY - glyph.yBearing;
371 penX += glyph.advance;
375 bool LayoutText( const LayoutParameters& layoutParameters,
376 Vector<Vector2>& glyphPositions,
377 Vector<LineRun>& lines,
380 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "-->LayoutText\n" );
381 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " box size %f, %f\n", layoutParameters.boundingBox.width, layoutParameters.boundingBox.height );
384 for( GlyphIndex index = 0u; index < layoutParameters.totalNumberOfGlyphs; )
386 // Get the layout for the line.
388 layout.glyphIndex = index;
389 GetLineLayoutForBox( layoutParameters,
393 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " glyph index %d\n", layout.glyphIndex );
394 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " character index %d\n", layout.characterIndex );
395 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " number of glyphs %d\n", layout.numberOfGlyphs );
396 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " number of characters %d\n", layout.numberOfCharacters );
397 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " length %f\n", layout.length );
399 if( 0u == layout.numberOfGlyphs )
401 // The width is too small and no characters are laid-out.
402 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--LayoutText width too small!\n\n" );
406 // Set the line position. Discard if ellipsis is enabled and the position exceeds the boundaries
408 penY += layout.ascender;
410 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " pen y %f\n", penY );
411 if( mEllipsisEnabled &&
412 ( ( penY - layout.descender > layoutParameters.boundingBox.height ) ||
413 ( ( mLayout == SINGLE_LINE_BOX ) &&
414 ( layout.length + layout.widthAdvanceDiff > layoutParameters.boundingBox.width ) ) ) )
416 // Do not layout more lines if ellipsis is enabled.
418 // The last line needs to be completely filled with characters.
419 // Part of a word may be used.
421 const Length numberOfLines = lines.Count();
424 LineLayout ellipsisLayout;
425 if( 0u != numberOfLines )
427 // Get the last line and layout it again with the 'completelyFill' flag to true.
428 lineRun = *( lines.Begin() + ( numberOfLines - 1u ) );
430 penY -= layout.ascender - lineRun.descender;
432 ellipsisLayout.glyphIndex = lineRun.glyphIndex;
436 lineRun.glyphIndex = 0u;
437 ellipsisLayout.glyphIndex = 0u;
440 GetLineLayoutForBox( layoutParameters,
444 lineRun.numberOfGlyphs = ellipsisLayout.numberOfGlyphs;
445 lineRun.characterRun.characterIndex = ellipsisLayout.characterIndex;
446 lineRun.characterRun.numberOfCharacters = ellipsisLayout.numberOfCharacters;
447 lineRun.width = layoutParameters.boundingBox.width;
448 lineRun.ascender = ellipsisLayout.ascender;
449 lineRun.descender = ellipsisLayout.descender;
450 lineRun.extraLength = ellipsisLayout.wsLengthEndOfLine > 0.f ? ellipsisLayout.wsLengthEndOfLine - ellipsisLayout.widthAdvanceDiff : 0.f;
451 lineRun.ellipsis = true;
453 actualSize.width = layoutParameters.boundingBox.width;
454 actualSize.height += ( lineRun.ascender + -lineRun.descender );
456 SetGlyphPositions( layoutParameters.glyphsBuffer + lineRun.glyphIndex,
457 ellipsisLayout.numberOfGlyphs,
459 glyphPositions.Begin() + lineRun.glyphIndex );
461 if( 0u != numberOfLines )
463 // Set the last line with the ellipsis layout.
464 *( lines.Begin() + ( numberOfLines - 1u ) ) = lineRun;
469 lines.PushBack( lineRun );
477 lineRun.glyphIndex = index;
478 lineRun.numberOfGlyphs = layout.numberOfGlyphs;
479 lineRun.characterRun.characterIndex = layout.characterIndex;
480 lineRun.characterRun.numberOfCharacters = layout.numberOfCharacters;
481 lineRun.width = layout.length + layout.widthAdvanceDiff;
482 lineRun.ascender = layout.ascender;
483 lineRun.descender = layout.descender;
484 lineRun.extraLength = layout.wsLengthEndOfLine > 0.f ? layout.wsLengthEndOfLine - layout.widthAdvanceDiff : 0.f;
485 lineRun.direction = false;
486 lineRun.ellipsis = false;
488 lines.PushBack( lineRun );
490 // Update the actual size.
491 if( lineRun.width > actualSize.width )
493 actualSize.width = lineRun.width;
496 actualSize.height += ( lineRun.ascender + -lineRun.descender );
498 SetGlyphPositions( layoutParameters.glyphsBuffer + index,
499 layout.numberOfGlyphs,
501 glyphPositions.Begin() + index );
503 penY += -layout.descender;
505 // Increase the glyph index.
506 index += layout.numberOfGlyphs;
510 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--LayoutText\n\n" );
515 void ReLayoutRightToLeftLines( const LayoutParameters& layoutParameters,
516 Vector<Vector2>& glyphPositions )
518 // Traverses the paragraphs with right to left characters.
519 for( LineIndex lineIndex = 0u; lineIndex < layoutParameters.numberOfBidirectionalInfoRuns; ++lineIndex )
521 const BidirectionalLineInfoRun& bidiLine = *( layoutParameters.lineBidirectionalInfoRunsBuffer + lineIndex );
525 const CharacterIndex characterVisualIndex = bidiLine.characterRun.characterIndex + *bidiLine.visualToLogicalMap;
526 const GlyphInfo& glyph = *( layoutParameters.glyphsBuffer + *( layoutParameters.charactersToGlyphsBuffer + characterVisualIndex ) );
528 penX = -glyph.xBearing;
530 Vector2* glyphPositionsBuffer = glyphPositions.Begin();
532 // Traverses the characters of the right to left paragraph.
533 for( CharacterIndex characterLogicalIndex = 0u;
534 characterLogicalIndex < bidiLine.characterRun.numberOfCharacters;
535 ++characterLogicalIndex )
537 // Convert the character in the logical order into the character in the visual order.
538 const CharacterIndex characterVisualIndex = bidiLine.characterRun.characterIndex + *( bidiLine.visualToLogicalMap + characterLogicalIndex );
540 // Get the number of glyphs of the character.
541 const Length numberOfGlyphs = *( layoutParameters.glyphsPerCharacterBuffer + characterVisualIndex );
543 for( GlyphIndex index = 0u; index < numberOfGlyphs; ++index )
545 // Convert the character in the visual order into the glyph in the visual order.
546 const GlyphIndex glyphIndex = *( layoutParameters.charactersToGlyphsBuffer + characterVisualIndex ) + index;
548 DALI_ASSERT_DEBUG( 0u <= glyphIndex && glyphIndex < layoutParameters.totalNumberOfGlyphs );
550 const GlyphInfo& glyph = *( layoutParameters.glyphsBuffer + glyphIndex );
551 Vector2& position = *( glyphPositionsBuffer + glyphIndex );
553 position.x = penX + glyph.xBearing;
554 penX += glyph.advance;
560 void Align( const Size& layoutSize,
561 Vector<LineRun>& lines )
563 // Traverse all lines and align the glyphs.
565 for( Vector<LineRun>::Iterator it = lines.Begin(), endIt = lines.End();
570 const bool isLastLine = lines.End() == it + 1u;
572 // Calculate the alignment offset accordingly with the align option,
573 // the box width, line length, and the paragraphs direction.
574 CalculateHorizontalAlignment( layoutSize.width,
580 void CalculateHorizontalAlignment( float boxWidth,
584 line.alignmentOffset = 0.f;
585 const bool isRTL = RTL == line.direction;
586 float lineLength = line.width;
588 HorizontalAlignment alignment = mHorizontalAlignment;
590 ( HORIZONTAL_ALIGN_CENTER != alignment ) )
592 if( HORIZONTAL_ALIGN_BEGIN == alignment )
594 alignment = HORIZONTAL_ALIGN_END;
598 alignment = HORIZONTAL_ALIGN_BEGIN;
604 case HORIZONTAL_ALIGN_BEGIN:
606 line.alignmentOffset = 0.f;
609 case HORIZONTAL_ALIGN_CENTER:
611 line.alignmentOffset = floorf( 0.5f * ( boxWidth - lineLength ) ); // try to avoid pixel alignment.
614 case HORIZONTAL_ALIGN_END:
616 line.alignmentOffset = boxWidth - lineLength;
623 line.alignmentOffset -= line.extraLength;
627 LayoutEngine::Layout mLayout;
628 LayoutEngine::HorizontalAlignment mHorizontalAlignment;
629 LayoutEngine::VerticalAlignment mVerticalAlignment;
631 TextAbstraction::FontClient mFontClient;
633 bool mEllipsisEnabled:1;
636 LayoutEngine::LayoutEngine()
639 mImpl = new LayoutEngine::Impl();
642 LayoutEngine::~LayoutEngine()
647 void LayoutEngine::SetLayout( Layout layout )
649 mImpl->mLayout = layout;
652 unsigned int LayoutEngine::GetLayout() const
654 return mImpl->mLayout;
657 void LayoutEngine::SetTextEllipsisEnabled( bool enabled )
659 mImpl->mEllipsisEnabled = enabled;
662 bool LayoutEngine::GetTextEllipsisEnabled() const
664 return mImpl->mEllipsisEnabled;
667 void LayoutEngine::SetHorizontalAlignment( HorizontalAlignment alignment )
669 mImpl->mHorizontalAlignment = alignment;
672 LayoutEngine::HorizontalAlignment LayoutEngine::GetHorizontalAlignment() const
674 return mImpl->mHorizontalAlignment;
677 void LayoutEngine::SetVerticalAlignment( VerticalAlignment alignment )
679 mImpl->mVerticalAlignment = alignment;
682 LayoutEngine::VerticalAlignment LayoutEngine::GetVerticalAlignment() const
684 return mImpl->mVerticalAlignment;
687 bool LayoutEngine::LayoutText( const LayoutParameters& layoutParameters,
688 Vector<Vector2>& glyphPositions,
689 Vector<LineRun>& lines,
692 return mImpl->LayoutText( layoutParameters,
698 void LayoutEngine::ReLayoutRightToLeftLines( const LayoutParameters& layoutParameters,
699 Vector<Vector2>& glyphPositions )
701 mImpl->ReLayoutRightToLeftLines( layoutParameters,
705 void LayoutEngine::Align( const Size& layoutSize,
706 Vector<LineRun>& lines )
708 mImpl->Align( layoutSize,
714 } // namespace Toolkit