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/public-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 )
103 mFontClient = TextAbstraction::FontClient::Get();
107 * @brief Updates the line ascender and descender with the metrics of a new font.
109 * @param[in] fontId The id of the new font.
110 * @param[in,out] lineLayout The line layout.
112 void UpdateLineHeight( FontId fontId, LineLayout& lineLayout )
114 Text::FontMetrics fontMetrics;
115 mFontClient.GetFontMetrics( fontId, fontMetrics );
117 // Sets the maximum ascender.
118 if( fontMetrics.ascender > lineLayout.ascender )
120 lineLayout.ascender = fontMetrics.ascender;
123 // Sets the minimum descender.
124 if( fontMetrics.descender < lineLayout.descender )
126 lineLayout.descender = fontMetrics.descender;
131 * @brief Merges a temporary line layout into the line layout.
133 * @param[in,out] lineLayout The line layout.
134 * @param[in] tmpLineLayout A temporary line layout.
136 void MergeLineLayout( LineLayout& lineLayout,
137 const LineLayout& tmpLineLayout )
139 lineLayout.numberOfCharacters += tmpLineLayout.numberOfCharacters;
140 lineLayout.numberOfGlyphs += tmpLineLayout.numberOfGlyphs;
141 lineLayout.length += tmpLineLayout.length;
143 if( 0.f < tmpLineLayout.length )
145 lineLayout.length += lineLayout.wsLengthEndOfLine;
147 lineLayout.wsLengthEndOfLine = tmpLineLayout.wsLengthEndOfLine;
148 lineLayout.widthAdvanceDiff = tmpLineLayout.widthAdvanceDiff;
152 lineLayout.wsLengthEndOfLine += tmpLineLayout.wsLengthEndOfLine;
155 if( tmpLineLayout.ascender > lineLayout.ascender )
157 lineLayout.ascender = tmpLineLayout.ascender;
160 if( tmpLineLayout.descender < lineLayout.descender )
162 lineLayout.descender = tmpLineLayout.descender;
167 * Retrieves the line layout for a given box width.
169 void GetLineLayoutForBox( const LayoutParameters& parameters,
170 LineLayout& lineLayout )
172 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "-->GetLineLayoutForBox\n" );
173 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " initial glyph index : %d\n", lineLayout.glyphIndex );
174 // Stores temporary line layout which has not been added to the final line layout.
175 LineLayout tmpLineLayout;
177 const bool isMultiline = mLayout == MULTI_LINE_BOX;
178 const GlyphIndex lastGlyphIndex = parameters.totalNumberOfGlyphs - 1u;
180 // If the first glyph has a negative bearing its absolute value needs to be added to the line length.
181 // In the case the line starts with a right to left character the bearing needs to be substracted to the line length.
182 const GlyphInfo& glyphInfo = *( parameters.glyphsBuffer + lineLayout.glyphIndex );
183 float initialHorizontalBearing = glyphInfo.xBearing;
185 lineLayout.characterIndex = *( parameters.glyphsToCharactersBuffer + lineLayout.glyphIndex );
186 const CharacterDirection firstCharacterDirection = ( NULL == parameters.characterDirectionBuffer ) ? false : *( parameters.characterDirectionBuffer + lineLayout.characterIndex );
188 if( RTL == firstCharacterDirection )
190 initialHorizontalBearing = -initialHorizontalBearing;
192 if( 0.f < glyphInfo.xBearing )
194 tmpLineLayout.length = glyphInfo.xBearing;
195 initialHorizontalBearing = 0.f;
200 if( 0.f > glyphInfo.xBearing )
202 tmpLineLayout.length = -glyphInfo.xBearing;
203 initialHorizontalBearing = 0.f;
207 // Calculate the line height if there is no characters.
208 FontId lastFontId = glyphInfo.fontId;
209 UpdateLineHeight( lastFontId, tmpLineLayout );
211 const float boundingBoxWidth = parameters.boundingBox.width - initialHorizontalBearing;
213 bool oneWordLaidOut = false;
215 for( GlyphIndex glyphIndex = lineLayout.glyphIndex;
216 glyphIndex < parameters.totalNumberOfGlyphs;
219 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " glyph index : %d\n", glyphIndex );
220 const bool isLastGlyph = glyphIndex == lastGlyphIndex;
222 // Get the glyph info.
223 const GlyphInfo& glyphInfo = *( parameters.glyphsBuffer + glyphIndex );
225 // Get the character indices for the current glyph. The last character index is needed
226 // because there are glyphs formed by more than one character but their break info is
227 // given only for the last character.
228 const Length charactersPerGlyph = *( parameters.charactersPerGlyphBuffer + glyphIndex );
229 const CharacterIndex characterFirstIndex = *( parameters.glyphsToCharactersBuffer + glyphIndex );
230 const CharacterIndex characterLastIndex = characterFirstIndex + ( ( 1u > charactersPerGlyph ) ? 0u : charactersPerGlyph - 1u );
232 // Get the line break info for the current character.
233 const LineBreakInfo lineBreakInfo = *( parameters.lineBreakInfoBuffer + characterLastIndex );
235 // Get the word break info for the current character.
236 const WordBreakInfo wordBreakInfo = *( parameters.wordBreakInfoBuffer + characterLastIndex );
238 // Increase the number of characters.
239 tmpLineLayout.numberOfCharacters += charactersPerGlyph;
241 // Increase the number of glyphs.
242 tmpLineLayout.numberOfGlyphs++;
244 // Check whether is a white space.
245 const Character character = *( parameters.textBuffer + characterFirstIndex );
246 const bool isWhiteSpace = TextAbstraction::IsWhiteSpace( character );
248 // Used to restore the temporal line layout when a single word does not fit in the control's width and is split by character.
249 const float previousTmpLineLength = tmpLineLayout.length;
250 const float previousTmpWidthAdvanceDiff = tmpLineLayout.widthAdvanceDiff;
252 // Increase the accumulated length.
255 // Add the length to the length of white spaces at the end of the line.
256 tmpLineLayout.wsLengthEndOfLine += glyphInfo.advance; // The advance is used as the width is always zero for the white spaces.
260 // Add as well any previous white space length.
261 tmpLineLayout.length += tmpLineLayout.wsLengthEndOfLine + glyphInfo.advance;
262 if( RTL == firstCharacterDirection )
264 tmpLineLayout.widthAdvanceDiff = -glyphInfo.xBearing;
268 tmpLineLayout.widthAdvanceDiff = glyphInfo.xBearing + glyphInfo.width - glyphInfo.advance;
271 // Clear the white space length at the end of the line.
272 tmpLineLayout.wsLengthEndOfLine = 0.f;
275 // Check if the accumulated length fits in the width of the box.
276 if( isMultiline && !isWhiteSpace &&
277 ( lineLayout.length + lineLayout.wsLengthEndOfLine + tmpLineLayout.length + tmpLineLayout.widthAdvanceDiff > boundingBoxWidth ) )
279 // Current word does not fit in the box's width.
280 if( !oneWordLaidOut )
282 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " Break the word by character\n" );
284 // The word's with doesn't fit in the control's with. It needs to be split by character.
285 if( tmpLineLayout.numberOfGlyphs > 1u )
287 tmpLineLayout.numberOfCharacters -= charactersPerGlyph;
288 --tmpLineLayout.numberOfGlyphs;
289 tmpLineLayout.length = previousTmpLineLength;
290 tmpLineLayout.widthAdvanceDiff = previousTmpWidthAdvanceDiff;
293 // Add part of the word to the line layout.
294 MergeLineLayout( lineLayout, tmpLineLayout );
298 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " Current word does not fit.\n" );
300 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--GetLineLayoutForBox\n" );
304 if( ( isMultiline || isLastGlyph ) &&
305 ( TextAbstraction::LINE_MUST_BREAK == lineBreakInfo ) )
307 // Must break the line. Update the line layout and return.
308 MergeLineLayout( lineLayout, tmpLineLayout );
310 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " Must break\n" );
311 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--GetLineLayoutForBox\n" );
316 ( TextAbstraction::WORD_BREAK == wordBreakInfo ) )
318 oneWordLaidOut = true;
319 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " One word laid out\n" );
321 // Current glyph is the last one of the current word.
322 // Add the temporal layout to the current one.
323 MergeLineLayout( lineLayout, tmpLineLayout );
325 tmpLineLayout.Clear();
328 // Check if the font of the current glyph is the same of the previous one.
329 // If it's different the ascender and descender need to be updated.
330 if( lastFontId != glyphInfo.fontId )
332 UpdateLineHeight( glyphInfo.fontId, tmpLineLayout );
333 lastFontId = glyphInfo.fontId;
336 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--GetLineLayoutForBox\n" );
339 bool LayoutText( const LayoutParameters& layoutParameters,
340 Vector<Vector2>& glyphPositions,
341 Vector<LineRun>& lines,
344 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "-->LayoutText\n" );
345 Vector2* glyphPositionsBuffer = glyphPositions.Begin();
348 for( GlyphIndex index = 0u; index < layoutParameters.totalNumberOfGlyphs; )
350 // Get the layout for the line.
352 layout.glyphIndex = index;
353 GetLineLayoutForBox( layoutParameters,
356 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " glyph index %d\n", layout.glyphIndex );
357 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " character index %d\n", layout.characterIndex );
358 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " number of glyphs %d\n", layout.numberOfGlyphs );
359 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " number of characters %d\n", layout.numberOfCharacters );
360 DALI_LOG_INFO( gLogFilter, Debug::Verbose, " length %f\n", layout.length );
362 if( 0u == layout.numberOfGlyphs )
364 // The width is too small and no characters are laid-out.
365 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--LayoutText width too small!\n\n" );
370 lineRun.glyphIndex = index;
371 lineRun.numberOfGlyphs = layout.numberOfGlyphs;
372 lineRun.characterRun.characterIndex = layout.characterIndex;
373 lineRun.characterRun.numberOfCharacters = layout.numberOfCharacters;
374 lineRun.width = layout.length + layout.widthAdvanceDiff;
375 lineRun.ascender = layout.ascender;
376 lineRun.descender = layout.descender;
377 lineRun.extraLength = layout.wsLengthEndOfLine > 0.f ? layout.wsLengthEndOfLine - layout.widthAdvanceDiff : 0.f;
378 lineRun.direction = false;
380 lines.PushBack( lineRun );
382 // Update the actual size.
383 if( lineRun.width > actualSize.width )
385 actualSize.width = lineRun.width;
388 actualSize.height += ( lineRun.ascender + -lineRun.descender );
390 // Traverse the glyphs and set the positions.
392 penY += layout.ascender;
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.
399 const GlyphInfo& glyph = *( layoutParameters.glyphsBuffer + index );
400 if( 0.f > glyph.xBearing )
402 penX = -glyph.xBearing;
405 for( GlyphIndex i = index; i < index + layout.numberOfGlyphs; ++i )
407 const GlyphInfo& glyph = *( layoutParameters.glyphsBuffer + i );
408 Vector2& position = *( glyphPositionsBuffer + i );
410 position.x = penX + glyph.xBearing;
411 position.y = penY - glyph.yBearing;
413 penX += glyph.advance;
416 penY += -layout.descender;
418 // Increase the glyph index.
419 index += layout.numberOfGlyphs;
422 DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--LayoutText\n\n" );
426 void ReLayoutRightToLeftLines( const LayoutParameters& layoutParameters,
427 Vector<Vector2>& glyphPositions )
429 // Traverses the paragraphs with right to left characters.
430 for( LineIndex lineIndex = 0u; lineIndex < layoutParameters.numberOfBidirectionalInfoRuns; ++lineIndex )
432 const BidirectionalLineInfoRun& bidiLine = *( layoutParameters.lineBidirectionalInfoRunsBuffer + lineIndex );
436 const CharacterIndex characterVisualIndex = bidiLine.characterRun.characterIndex + *bidiLine.visualToLogicalMap;
437 const GlyphInfo& glyph = *( layoutParameters.glyphsBuffer + *( layoutParameters.charactersToGlyphsBuffer + characterVisualIndex ) );
439 penX = -glyph.xBearing;
441 Vector2* glyphPositionsBuffer = glyphPositions.Begin();
443 // Traverses the characters of the right to left paragraph.
444 for( CharacterIndex characterLogicalIndex = 0u;
445 characterLogicalIndex < bidiLine.characterRun.numberOfCharacters;
446 ++characterLogicalIndex )
448 // Convert the character in the logical order into the character in the visual order.
449 const CharacterIndex characterVisualIndex = bidiLine.characterRun.characterIndex + *( bidiLine.visualToLogicalMap + characterLogicalIndex );
451 // Get the number of glyphs of the character.
452 const Length numberOfGlyphs = *( layoutParameters.glyphsPerCharacterBuffer + characterVisualIndex );
454 for( GlyphIndex index = 0u; index < numberOfGlyphs; ++index )
456 // Convert the character in the visual order into the glyph in the visual order.
457 const GlyphIndex glyphIndex = *( layoutParameters.charactersToGlyphsBuffer + characterVisualIndex ) + index;
459 DALI_ASSERT_DEBUG( 0u <= glyphIndex && glyphIndex < layoutParameters.totalNumberOfGlyphs );
461 const GlyphInfo& glyph = *( layoutParameters.glyphsBuffer + glyphIndex );
462 Vector2& position = *( glyphPositionsBuffer + glyphIndex );
464 position.x = penX + glyph.xBearing;
465 penX += glyph.advance;
471 void Align( const LayoutParameters& layoutParameters,
472 const Size& layoutSize,
473 const Vector<LineRun>& lines,
474 Vector<Vector2>& glyphPositions )
476 Vector2* glyphPositionsBuffer = glyphPositions.Begin();
478 // Traverse all lines and align the glyphs.
479 // LayoutParameters contains bidirectional info for those lines with
480 // right to left text, this info includes the paragraph's direction.
482 LineIndex bidiLineIndex = 0u;
483 for( Vector<LineRun>::ConstIterator it = lines.Begin(), endIt = lines.End();
487 const LineRun& line = *it;
489 // 1) Get the paragrap's direction.
490 bool paragraphDirection = false;
492 // Check if there is any right to left line.
493 if( ( NULL != layoutParameters.lineBidirectionalInfoRunsBuffer ) &&
494 ( bidiLineIndex < layoutParameters.numberOfBidirectionalInfoRuns ) )
496 const BidirectionalLineInfoRun* bidiLine = layoutParameters.lineBidirectionalInfoRunsBuffer + bidiLineIndex;
498 // Get the right to left line that match with current line.
499 while( ( line.characterRun.characterIndex > bidiLine->characterRun.characterIndex ) &&
500 ( bidiLineIndex < layoutParameters.numberOfBidirectionalInfoRuns ) )
503 bidiLine = layoutParameters.lineBidirectionalInfoRunsBuffer + bidiLineIndex;
506 if( line.characterRun.characterIndex == bidiLine->characterRun.characterIndex )
508 paragraphDirection = bidiLine->direction;
512 // 2) Calculate the alignment offset accordingly with the align option,
513 // the box width, line length, and the paragraphs direction.
514 float alignOffset = CalculateHorizontalAlignment( layoutSize.width,
517 paragraphDirection );
519 // 3) Traverse all glyphs and update the 'x' position.
520 for( GlyphIndex index = line.glyphIndex,
521 endIndex = line.glyphIndex + line.numberOfGlyphs;
525 Vector2& position = *( glyphPositionsBuffer + index );
527 position.x += alignOffset;
532 float CalculateHorizontalAlignment( float boxWidth,
535 bool paragraphDirection )
539 HorizontalAlignment alignment = mHorizontalAlignment;
540 if( paragraphDirection &&
541 ( HORIZONTAL_ALIGN_CENTER != alignment ) )
543 if( HORIZONTAL_ALIGN_BEGIN == alignment )
545 alignment = HORIZONTAL_ALIGN_END;
549 alignment = HORIZONTAL_ALIGN_BEGIN;
555 case HORIZONTAL_ALIGN_BEGIN:
560 case HORIZONTAL_ALIGN_CENTER:
562 offset = 0.5f * ( boxWidth - lineLength );
563 const int intOffset = static_cast<int>( offset ); // try to avoid pixel alignment.
564 offset = static_cast<float>( intOffset );
567 case HORIZONTAL_ALIGN_END:
569 offset = boxWidth - lineLength;
574 if( paragraphDirection )
576 offset -= extraLength;
582 LayoutEngine::Layout mLayout;
583 LayoutEngine::HorizontalAlignment mHorizontalAlignment;
584 LayoutEngine::VerticalAlignment mVerticalAlignment;
586 TextAbstraction::FontClient mFontClient;
589 LayoutEngine::LayoutEngine()
592 mImpl = new LayoutEngine::Impl();
595 LayoutEngine::~LayoutEngine()
600 void LayoutEngine::SetLayout( Layout layout )
602 mImpl->mLayout = layout;
605 unsigned int LayoutEngine::GetLayout() const
607 return mImpl->mLayout;
610 void LayoutEngine::SetHorizontalAlignment( HorizontalAlignment alignment )
612 mImpl->mHorizontalAlignment = alignment;
615 LayoutEngine::HorizontalAlignment LayoutEngine::GetHorizontalAlignment() const
617 return mImpl->mHorizontalAlignment;
620 void LayoutEngine::SetVerticalAlignment( VerticalAlignment alignment )
622 mImpl->mVerticalAlignment = alignment;
625 LayoutEngine::VerticalAlignment LayoutEngine::GetVerticalAlignment() const
627 return mImpl->mVerticalAlignment;
630 bool LayoutEngine::LayoutText( const LayoutParameters& layoutParameters,
631 Vector<Vector2>& glyphPositions,
632 Vector<LineRun>& lines,
635 return mImpl->LayoutText( layoutParameters,
641 void LayoutEngine::ReLayoutRightToLeftLines( const LayoutParameters& layoutParameters,
642 Vector<Vector2>& glyphPositions )
644 mImpl->ReLayoutRightToLeftLines( layoutParameters,
648 void LayoutEngine::Align( const LayoutParameters& layoutParameters,
649 const Size& layoutSize,
650 const Vector<LineRun>& lines,
651 Vector<Vector2>& glyphPositions )
653 mImpl->Align( layoutParameters,
661 } // namespace Toolkit