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>
27 #include <dali-toolkit/internal/text/layouts/layout-parameters.h>
28 #include <dali-toolkit/internal/text/bidirectional-line-info-run.h>
42 const float MAX_FLOAT = std::numeric_limits<float>::max();
43 const bool RTL = true;
48 * @brief Stores temporary layout info of the line.
55 numberOfCharacters( 0u ),
58 widthAdvanceDiff( 0.f ),
59 wsLengthEndOfLine( 0.f ),
61 descender( MAX_FLOAT )
71 numberOfCharacters = 0u;
74 widthAdvanceDiff = 0.f;
75 wsLengthEndOfLine = 0.f;
77 descender = MAX_FLOAT;
80 GlyphIndex glyphIndex; ///< Index of the first glyph to be laid-out.
81 CharacterIndex characterIndex; ///< Index of the first character to be laid-out.
82 Length numberOfCharacters; ///< The number of characters which fit in one line.
83 Length numberOfGlyphs; ///< The number of glyph which fit in one line.
84 float length; ///< The length of the glyphs which fit in one line.
85 float widthAdvanceDiff; ///< The difference between the xBearing + width and the advance of the last glyph.
86 float wsLengthEndOfLine; ///< The length of the white spaces at the end of the line.
87 float ascender; ///< The maximum ascender of all fonts in the line.
88 float descender; ///< The minimum descender of all fonts in the line.
91 struct LayoutEngine::Impl
94 : mLayout( LayoutEngine::SINGLE_LINE_BOX ),
95 mHorizontalAlignment( LayoutEngine::HORIZONTAL_ALIGN_BEGIN ),
96 mVerticalAlignment( LayoutEngine::VERTICAL_ALIGN_TOP )
98 mFontClient = TextAbstraction::FontClient::Get();
102 * @brief Updates the line ascender and descender with the metrics of a new font.
104 * @param[in] fontId The id of the new font.
105 * @param[in,out] lineLayout The line layout.
107 void UpdateLineHeight( FontId fontId, LineLayout& lineLayout )
109 Text::FontMetrics fontMetrics;
110 mFontClient.GetFontMetrics( fontId, fontMetrics );
112 // Sets the maximum ascender.
113 if( fontMetrics.ascender > lineLayout.ascender )
115 lineLayout.ascender = fontMetrics.ascender;
118 // Sets the minimum descender.
119 if( fontMetrics.descender < lineLayout.descender )
121 lineLayout.descender = fontMetrics.descender;
126 * @brief Merges a temporary line layout into the line layout.
128 * @param[in,out] lineLayout The line layout.
129 * @param[in] tmpLineLayout A temporary line layout.
131 void MergeLineLayout( LineLayout& lineLayout,
132 const LineLayout& tmpLineLayout )
134 lineLayout.numberOfCharacters += tmpLineLayout.numberOfCharacters;
135 lineLayout.numberOfGlyphs += tmpLineLayout.numberOfGlyphs;
136 lineLayout.length += tmpLineLayout.length;
138 if( 0.f < tmpLineLayout.length )
140 lineLayout.length += lineLayout.wsLengthEndOfLine;
142 lineLayout.wsLengthEndOfLine = tmpLineLayout.wsLengthEndOfLine;
143 lineLayout.widthAdvanceDiff = tmpLineLayout.widthAdvanceDiff;
147 lineLayout.wsLengthEndOfLine += tmpLineLayout.wsLengthEndOfLine;
150 if( tmpLineLayout.ascender > lineLayout.ascender )
152 lineLayout.ascender = tmpLineLayout.ascender;
155 if( tmpLineLayout.descender < lineLayout.descender )
157 lineLayout.descender = tmpLineLayout.descender;
162 * Retrieves the line layout for a given box width.
164 void GetLineLayoutForBox( const LayoutParameters& parameters,
165 LineLayout& lineLayout )
167 // Stores temporary line layout which has not been added to the final line layout.
168 LineLayout tmpLineLayout;
170 const bool isMultiline = mLayout == MULTI_LINE_BOX;
171 const GlyphIndex lastGlyphIndex = parameters.totalNumberOfGlyphs - 1u;
173 // If the first glyph has a negative bearing its absolute value needs to be added to the line length.
174 // In the case the line starts with a right to left character the bearing needs to be substracted to the line length.
175 const GlyphInfo& glyphInfo = *( parameters.glyphsBuffer + lineLayout.glyphIndex );
176 float initialHorizontalBearing = glyphInfo.xBearing;
178 lineLayout.characterIndex = *( parameters.glyphsToCharactersBuffer + lineLayout.glyphIndex );
179 const CharacterDirection firstCharacterDirection = ( NULL == parameters.characterDirectionBuffer ) ? false : *( parameters.characterDirectionBuffer + lineLayout.characterIndex );
181 if( RTL == firstCharacterDirection )
183 initialHorizontalBearing = -initialHorizontalBearing;
185 if( 0.f < glyphInfo.xBearing )
187 tmpLineLayout.length = glyphInfo.xBearing;
188 initialHorizontalBearing = 0.f;
193 if( 0.f > glyphInfo.xBearing )
195 tmpLineLayout.length = -glyphInfo.xBearing;
196 initialHorizontalBearing = 0.f;
200 // Calculate the line height if there is no characters.
201 FontId lastFontId = glyphInfo.fontId;
202 UpdateLineHeight( lastFontId, tmpLineLayout );
204 const float boundingBoxWidth = parameters.boundingBox.width - initialHorizontalBearing;
206 bool oneWordLaidOut = false;
208 for( GlyphIndex glyphIndex = lineLayout.glyphIndex;
209 glyphIndex < parameters.totalNumberOfGlyphs;
212 const bool isLastGlyph = glyphIndex == lastGlyphIndex;
214 // Get the glyph info.
215 const GlyphInfo& glyphInfo = *( parameters.glyphsBuffer + glyphIndex );
217 // Get the character indices for the current glyph. The last character index is needed
218 // because there are glyphs formed by more than one character but their break info is
219 // given only for the last character.
220 const Length charactersPerGlyph = *( parameters.charactersPerGlyphBuffer + glyphIndex );
221 const CharacterIndex characterFirstIndex = *( parameters.glyphsToCharactersBuffer + glyphIndex );
222 const CharacterIndex characterLastIndex = characterFirstIndex + ( ( 1u > charactersPerGlyph ) ? 0u : charactersPerGlyph - 1u );
224 // Get the line break info for the current character.
225 const LineBreakInfo lineBreakInfo = *( parameters.lineBreakInfoBuffer + characterLastIndex );
227 // Get the word break info for the current character.
228 const WordBreakInfo wordBreakInfo = *( parameters.wordBreakInfoBuffer + characterLastIndex );
230 // Increase the number of characters.
231 tmpLineLayout.numberOfCharacters += charactersPerGlyph;
233 // Increase the number of glyphs.
234 tmpLineLayout.numberOfGlyphs++;
236 // Check whether is a white space.
237 const Character character = *( parameters.textBuffer + characterFirstIndex );
238 const bool isWhiteSpace = TextAbstraction::IsWhiteSpace( character );
240 // Increase the accumulated length.
243 // Add the length to the length of white spaces at the end of the line.
244 tmpLineLayout.wsLengthEndOfLine += glyphInfo.advance; // The advance is used as the width is always zero for the white spaces.
248 // Add as well any previous white space length.
249 tmpLineLayout.length += tmpLineLayout.wsLengthEndOfLine + glyphInfo.advance;
250 if( RTL == firstCharacterDirection )
252 tmpLineLayout.widthAdvanceDiff = -glyphInfo.xBearing;
256 tmpLineLayout.widthAdvanceDiff = glyphInfo.xBearing + glyphInfo.width - glyphInfo.advance;
259 // Clear the white space length at the end of the line.
260 tmpLineLayout.wsLengthEndOfLine = 0.f;
263 // Check if the accumulated length fits in the width of the box.
264 if( isMultiline && oneWordLaidOut && !isWhiteSpace &&
265 ( lineLayout.length + lineLayout.wsLengthEndOfLine + tmpLineLayout.length + tmpLineLayout.widthAdvanceDiff > boundingBoxWidth ) )
267 // Current word does not fit in the box's width.
271 if( ( isMultiline || isLastGlyph ) &&
272 ( TextAbstraction::LINE_MUST_BREAK == lineBreakInfo ) )
274 // Must break the line. Update the line layout and return.
275 MergeLineLayout( lineLayout, tmpLineLayout );
281 ( TextAbstraction::WORD_BREAK == wordBreakInfo ) )
283 if( !oneWordLaidOut && !isWhiteSpace )
285 oneWordLaidOut = true;
288 // Current glyph is the last one of the current word.
289 // Add the temporal layout to the current one.
290 MergeLineLayout( lineLayout, tmpLineLayout );
292 tmpLineLayout.Clear();
295 // Check if the font of the current glyph is the same of the previous one.
296 // If it's different the ascender and descender need to be updated.
297 if( lastFontId != glyphInfo.fontId )
299 UpdateLineHeight( glyphInfo.fontId, tmpLineLayout );
300 lastFontId = glyphInfo.fontId;
305 bool LayoutText( const LayoutParameters& layoutParameters,
306 Vector<Vector2>& glyphPositions,
307 Vector<LineRun>& lines,
310 Vector2* glyphPositionsBuffer = glyphPositions.Begin();
313 for( GlyphIndex index = 0u; index < layoutParameters.totalNumberOfGlyphs; )
315 // Get the layout for the line.
317 layout.glyphIndex = index;
318 GetLineLayoutForBox( layoutParameters,
321 if( 0u == layout.numberOfGlyphs )
323 // The width is too small and no characters are laid-out.
328 lineRun.glyphIndex = index;
329 lineRun.numberOfGlyphs = layout.numberOfGlyphs;
330 lineRun.characterRun.characterIndex = layout.characterIndex;
331 lineRun.characterRun.numberOfCharacters = layout.numberOfCharacters;
332 lineRun.width = layout.length + layout.widthAdvanceDiff;
333 lineRun.ascender = layout.ascender;
334 lineRun.descender = layout.descender;
335 lineRun.extraLength = layout.wsLengthEndOfLine > 0.f ? layout.wsLengthEndOfLine - layout.widthAdvanceDiff : 0.f;
336 lineRun.direction = false;
338 lines.PushBack( lineRun );
340 // Update the actual size.
341 if( lineRun.width > actualSize.width )
343 actualSize.width = lineRun.width;
346 actualSize.height += ( lineRun.ascender + -lineRun.descender );
348 // Traverse the glyphs and set the positions.
350 penY += layout.ascender;
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 = *( layoutParameters.glyphsBuffer + index );
358 if( 0.f > glyph.xBearing )
360 penX = -glyph.xBearing;
363 for( GlyphIndex i = index; i < index + layout.numberOfGlyphs; ++i )
365 const GlyphInfo& glyph = *( layoutParameters.glyphsBuffer + i );
366 Vector2& position = *( glyphPositionsBuffer + i );
368 position.x = penX + glyph.xBearing;
369 position.y = penY - glyph.yBearing;
371 penX += glyph.advance;
374 penY += -layout.descender;
376 // Increase the glyph index.
377 index += layout.numberOfGlyphs;
383 void ReLayoutRightToLeftLines( const LayoutParameters& layoutParameters,
384 Vector<Vector2>& glyphPositions )
386 // Traverses the paragraphs with right to left characters.
387 for( LineIndex lineIndex = 0u; lineIndex < layoutParameters.numberOfBidirectionalInfoRuns; ++lineIndex )
389 const BidirectionalLineInfoRun& bidiLine = *( layoutParameters.lineBidirectionalInfoRunsBuffer + lineIndex );
393 const CharacterIndex characterVisualIndex = bidiLine.characterRun.characterIndex + *bidiLine.visualToLogicalMap;
394 const GlyphInfo& glyph = *( layoutParameters.glyphsBuffer + *( layoutParameters.charactersToGlyphsBuffer + characterVisualIndex ) );
396 penX = -glyph.xBearing;
398 Vector2* glyphPositionsBuffer = glyphPositions.Begin();
400 // Traverses the characters of the right to left paragraph.
401 for( CharacterIndex characterLogicalIndex = 0u;
402 characterLogicalIndex < bidiLine.characterRun.numberOfCharacters;
403 ++characterLogicalIndex )
405 // Convert the character in the logical order into the character in the visual order.
406 const CharacterIndex characterVisualIndex = bidiLine.characterRun.characterIndex + *( bidiLine.visualToLogicalMap + characterLogicalIndex );
408 // Get the number of glyphs of the character.
409 const Length numberOfGlyphs = *( layoutParameters.glyphsPerCharacterBuffer + characterVisualIndex );
411 for( GlyphIndex index = 0u; index < numberOfGlyphs; ++index )
413 // Convert the character in the visual order into the glyph in the visual order.
414 const GlyphIndex glyphIndex = *( layoutParameters.charactersToGlyphsBuffer + characterVisualIndex ) + index;
416 DALI_ASSERT_DEBUG( 0u <= glyphIndex && glyphIndex < layoutParameters.totalNumberOfGlyphs );
418 const GlyphInfo& glyph = *( layoutParameters.glyphsBuffer + glyphIndex );
419 Vector2& position = *( glyphPositionsBuffer + glyphIndex );
421 position.x = penX + glyph.xBearing;
422 penX += glyph.advance;
428 void Align( const LayoutParameters& layoutParameters,
429 const Size& layoutSize,
430 const Vector<LineRun>& lines,
431 Vector<Vector2>& glyphPositions )
433 Vector2* glyphPositionsBuffer = glyphPositions.Begin();
435 // Traverse all lines and align the glyphs.
436 // LayoutParameters contains bidirectional info for those lines with
437 // right to left text, this info includes the paragraph's direction.
439 LineIndex bidiLineIndex = 0u;
440 for( Vector<LineRun>::ConstIterator it = lines.Begin(), endIt = lines.End();
444 const LineRun& line = *it;
446 // 1) Get the paragrap's direction.
447 bool paragraphDirection = false;
449 // Check if there is any right to left line.
450 if( ( NULL != layoutParameters.lineBidirectionalInfoRunsBuffer ) &&
451 ( bidiLineIndex < layoutParameters.numberOfBidirectionalInfoRuns ) )
453 const BidirectionalLineInfoRun* bidiLine = layoutParameters.lineBidirectionalInfoRunsBuffer + bidiLineIndex;
455 // Get the right to left line that match with current line.
456 while( ( line.characterRun.characterIndex > bidiLine->characterRun.characterIndex ) &&
457 ( bidiLineIndex < layoutParameters.numberOfBidirectionalInfoRuns ) )
460 bidiLine = layoutParameters.lineBidirectionalInfoRunsBuffer + bidiLineIndex;
463 if( line.characterRun.characterIndex == bidiLine->characterRun.characterIndex )
465 paragraphDirection = bidiLine->direction;
469 // 2) Calculate the alignment offset accordingly with the align option,
470 // the box width, line length, and the paragraphs direction.
471 float alignOffset = CalculateHorizontalAlignment( layoutSize.width,
474 paragraphDirection );
476 // 3) Traverse all glyphs and update the 'x' position.
477 for( GlyphIndex index = line.glyphIndex,
478 endIndex = line.glyphIndex + line.numberOfGlyphs;
482 Vector2& position = *( glyphPositionsBuffer + index );
484 position.x += alignOffset;
489 float CalculateHorizontalAlignment( float boxWidth,
492 bool paragraphDirection )
496 HorizontalAlignment alignment = mHorizontalAlignment;
497 if( paragraphDirection &&
498 ( HORIZONTAL_ALIGN_CENTER != alignment ) )
500 if( HORIZONTAL_ALIGN_BEGIN == alignment )
502 alignment = HORIZONTAL_ALIGN_END;
506 alignment = HORIZONTAL_ALIGN_BEGIN;
512 case HORIZONTAL_ALIGN_BEGIN:
517 case HORIZONTAL_ALIGN_CENTER:
519 offset = 0.5f * ( boxWidth - lineLength );
520 const int intOffset = static_cast<int>( offset ); // try to avoid pixel alignment.
521 offset = static_cast<float>( intOffset );
524 case HORIZONTAL_ALIGN_END:
526 offset = boxWidth - lineLength;
531 if( paragraphDirection )
533 offset -= extraLength;
539 LayoutEngine::Layout mLayout;
540 LayoutEngine::HorizontalAlignment mHorizontalAlignment;
541 LayoutEngine::VerticalAlignment mVerticalAlignment;
543 TextAbstraction::FontClient mFontClient;
546 LayoutEngine::LayoutEngine()
549 mImpl = new LayoutEngine::Impl();
552 LayoutEngine::~LayoutEngine()
557 void LayoutEngine::SetLayout( Layout layout )
559 mImpl->mLayout = layout;
562 unsigned int LayoutEngine::GetLayout() const
564 return mImpl->mLayout;
567 void LayoutEngine::SetHorizontalAlignment( HorizontalAlignment alignment )
569 mImpl->mHorizontalAlignment = alignment;
572 LayoutEngine::HorizontalAlignment LayoutEngine::GetHorizontalAlignment() const
574 return mImpl->mHorizontalAlignment;
577 void LayoutEngine::SetVerticalAlignment( VerticalAlignment alignment )
579 mImpl->mVerticalAlignment = alignment;
582 LayoutEngine::VerticalAlignment LayoutEngine::GetVerticalAlignment() const
584 return mImpl->mVerticalAlignment;
587 bool LayoutEngine::LayoutText( const LayoutParameters& layoutParameters,
588 Vector<Vector2>& glyphPositions,
589 Vector<LineRun>& lines,
592 return mImpl->LayoutText( layoutParameters,
598 void LayoutEngine::ReLayoutRightToLeftLines( const LayoutParameters& layoutParameters,
599 Vector<Vector2>& glyphPositions )
601 mImpl->ReLayoutRightToLeftLines( layoutParameters,
605 void LayoutEngine::Align( const LayoutParameters& layoutParameters,
606 const Size& layoutSize,
607 const Vector<LineRun>& lines,
608 Vector<Vector2>& glyphPositions )
610 mImpl->Align( layoutParameters,
618 } // namespace Toolkit