Layout fixes
[platform/core/uifw/dali-toolkit.git] / dali-toolkit / internal / text / layouts / layout-engine.cpp
1 /*
2  * Copyright (c) 2015 Samsung Electronics Co., Ltd.
3  *
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
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  *
16  */
17
18 // CLASS HEADER
19 #include <dali-toolkit/internal/text/layouts/layout-engine.h>
20
21 // EXTERNAL INCLUDES
22 #include <limits>
23 #include <dali/public-api/math/vector2.h>
24 #include <dali/public-api/text-abstraction/font-client.h>
25
26 // INTERNAL INCLUDES
27 #include <dali-toolkit/internal/text/layouts/layout-parameters.h>
28 #include <dali-toolkit/internal/text/bidirectional-line-info-run.h>
29
30 namespace Dali
31 {
32
33 namespace Toolkit
34 {
35
36 namespace Text
37 {
38
39 namespace
40 {
41
42 const float MAX_FLOAT = std::numeric_limits<float>::max();
43 const bool RTL = true;
44
45 } //namespace
46
47 /**
48  * @brief Stores temporary layout info of the line.
49  */
50 struct LineLayout
51 {
52   LineLayout()
53   : glyphIndex( 0u ),
54     characterIndex( 0u ),
55     numberOfCharacters( 0u ),
56     numberOfGlyphs( 0u ),
57     length( 0.f ),
58     widthAdvanceDiff( 0.f ),
59     wsLengthEndOfLine( 0.f ),
60     ascender( 0.f ),
61     descender( MAX_FLOAT )
62   {}
63
64   ~LineLayout()
65   {}
66
67   void Clear()
68   {
69     glyphIndex = 0u;
70     characterIndex = 0u;
71     numberOfCharacters = 0u;
72     numberOfGlyphs = 0u;
73     length = 0.f;
74     widthAdvanceDiff = 0.f;
75     wsLengthEndOfLine = 0.f;
76     ascender = 0.f;
77     descender = MAX_FLOAT;
78   }
79
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.
89 };
90
91 struct LayoutEngine::Impl
92 {
93   Impl()
94   : mLayout( LayoutEngine::SINGLE_LINE_BOX ),
95     mHorizontalAlignment( LayoutEngine::HORIZONTAL_ALIGN_BEGIN ),
96     mVerticalAlignment( LayoutEngine::VERTICAL_ALIGN_TOP )
97   {
98     mFontClient = TextAbstraction::FontClient::Get();
99   }
100
101   /**
102    * @brief Updates the line ascender and descender with the metrics of a new font.
103    *
104    * @param[in] fontId The id of the new font.
105    * @param[in,out] lineLayout The line layout.
106    */
107   void UpdateLineHeight( FontId fontId, LineLayout& lineLayout )
108   {
109     Text::FontMetrics fontMetrics;
110     mFontClient.GetFontMetrics( fontId, fontMetrics );
111
112     // Sets the maximum ascender.
113     if( fontMetrics.ascender > lineLayout.ascender )
114     {
115       lineLayout.ascender = fontMetrics.ascender;
116     }
117
118     // Sets the minimum descender.
119     if( fontMetrics.descender < lineLayout.descender )
120     {
121       lineLayout.descender = fontMetrics.descender;
122     }
123   }
124
125   /**
126    * @brief Merges a temporary line layout into the line layout.
127    *
128    * @param[in,out] lineLayout The line layout.
129    * @param[in] tmpLineLayout A temporary line layout.
130    */
131   void MergeLineLayout( LineLayout& lineLayout,
132                         const LineLayout& tmpLineLayout )
133   {
134     lineLayout.numberOfCharacters += tmpLineLayout.numberOfCharacters;
135     lineLayout.numberOfGlyphs += tmpLineLayout.numberOfGlyphs;
136     lineLayout.length += tmpLineLayout.length;
137
138     if( 0.f < tmpLineLayout.length )
139     {
140       lineLayout.length += lineLayout.wsLengthEndOfLine;
141
142       lineLayout.wsLengthEndOfLine = tmpLineLayout.wsLengthEndOfLine;
143       lineLayout.widthAdvanceDiff = tmpLineLayout.widthAdvanceDiff;
144     }
145     else
146     {
147       lineLayout.wsLengthEndOfLine += tmpLineLayout.wsLengthEndOfLine;
148     }
149
150     if( tmpLineLayout.ascender > lineLayout.ascender )
151     {
152       lineLayout.ascender = tmpLineLayout.ascender;
153     }
154
155     if( tmpLineLayout.descender < lineLayout.descender )
156     {
157       lineLayout.descender = tmpLineLayout.descender;
158     }
159   }
160
161   /**
162    * Retrieves the line layout for a given box width.
163    */
164   void GetLineLayoutForBox( const LayoutParameters& parameters,
165                             LineLayout& lineLayout )
166   {
167     // Stores temporary line layout which has not been added to the final line layout.
168     LineLayout tmpLineLayout;
169
170     const bool isMultiline = mLayout == MULTI_LINE_BOX;
171     const GlyphIndex lastGlyphIndex = parameters.totalNumberOfGlyphs - 1u;
172
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;
177
178     lineLayout.characterIndex = *( parameters.glyphsToCharactersBuffer + lineLayout.glyphIndex );
179     const CharacterDirection firstCharacterDirection = ( NULL == parameters.characterDirectionBuffer ) ? false : *( parameters.characterDirectionBuffer + lineLayout.characterIndex );
180
181     if( RTL == firstCharacterDirection )
182     {
183       initialHorizontalBearing = -initialHorizontalBearing;
184
185       if( 0.f < glyphInfo.xBearing )
186       {
187         tmpLineLayout.length = glyphInfo.xBearing;
188         initialHorizontalBearing = 0.f;
189       }
190     }
191     else
192     {
193       if( 0.f > glyphInfo.xBearing )
194       {
195         tmpLineLayout.length = -glyphInfo.xBearing;
196         initialHorizontalBearing = 0.f;
197       }
198     }
199
200     // Calculate the line height if there is no characters.
201     FontId lastFontId = glyphInfo.fontId;
202     UpdateLineHeight( lastFontId, tmpLineLayout );
203
204     const float boundingBoxWidth = parameters.boundingBox.width - initialHorizontalBearing;
205
206     bool oneWordLaidOut = false;
207
208     for( GlyphIndex glyphIndex = lineLayout.glyphIndex;
209          glyphIndex < parameters.totalNumberOfGlyphs;
210          ++glyphIndex )
211     {
212       const bool isLastGlyph = glyphIndex == lastGlyphIndex;
213
214       // Get the glyph info.
215       const GlyphInfo& glyphInfo = *( parameters.glyphsBuffer + glyphIndex );
216
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 );
223
224       // Get the line break info for the current character.
225       const LineBreakInfo lineBreakInfo = *( parameters.lineBreakInfoBuffer + characterLastIndex );
226
227       // Get the word break info for the current character.
228       const WordBreakInfo wordBreakInfo = *( parameters.wordBreakInfoBuffer + characterLastIndex );
229
230       // Increase the number of characters.
231       tmpLineLayout.numberOfCharacters += charactersPerGlyph;
232
233       // Increase the number of glyphs.
234       tmpLineLayout.numberOfGlyphs++;
235
236       // Check whether is a white space.
237       const Character character = *( parameters.textBuffer + characterFirstIndex );
238       const bool isWhiteSpace = TextAbstraction::IsWhiteSpace( character );
239
240       // Increase the accumulated length.
241       if( isWhiteSpace )
242       {
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.
245       }
246       else
247       {
248         // Add as well any previous white space length.
249         tmpLineLayout.length += tmpLineLayout.wsLengthEndOfLine + glyphInfo.advance;
250         if( RTL == firstCharacterDirection )
251         {
252           tmpLineLayout.widthAdvanceDiff = -glyphInfo.xBearing;
253         }
254         else
255         {
256           tmpLineLayout.widthAdvanceDiff = glyphInfo.xBearing + glyphInfo.width - glyphInfo.advance;
257         }
258
259         // Clear the white space length at the end of the line.
260         tmpLineLayout.wsLengthEndOfLine = 0.f;
261       }
262
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 ) )
266       {
267         // Current word does not fit in the box's width.
268         return;
269       }
270
271       if( ( isMultiline || isLastGlyph ) &&
272           ( TextAbstraction::LINE_MUST_BREAK == lineBreakInfo ) )
273       {
274         // Must break the line. Update the line layout and return.
275         MergeLineLayout( lineLayout, tmpLineLayout );
276
277         return;
278       }
279
280       if( isMultiline &&
281           ( TextAbstraction::WORD_BREAK == wordBreakInfo ) )
282       {
283         if( !oneWordLaidOut && !isWhiteSpace )
284         {
285           oneWordLaidOut = true;
286         }
287
288         // Current glyph is the last one of the current word.
289         // Add the temporal layout to the current one.
290         MergeLineLayout( lineLayout, tmpLineLayout );
291
292         tmpLineLayout.Clear();
293       }
294
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 )
298       {
299         UpdateLineHeight( glyphInfo.fontId, tmpLineLayout );
300         lastFontId = glyphInfo.fontId;
301       }
302     }
303   }
304
305   bool LayoutText( const LayoutParameters& layoutParameters,
306                    Vector<Vector2>& glyphPositions,
307                    Vector<LineRun>& lines,
308                    Size& actualSize )
309   {
310     Vector2* glyphPositionsBuffer = glyphPositions.Begin();
311
312     float penY = 0.f;
313     for( GlyphIndex index = 0u; index < layoutParameters.totalNumberOfGlyphs; )
314     {
315       // Get the layout for the line.
316       LineLayout layout;
317       layout.glyphIndex = index;
318       GetLineLayoutForBox( layoutParameters,
319                            layout );
320
321       if( 0u == layout.numberOfGlyphs )
322       {
323         // The width is too small and no characters are laid-out.
324         return false;
325       }
326
327       LineRun lineRun;
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;
337
338       lines.PushBack( lineRun );
339
340       // Update the actual size.
341       if( lineRun.width > actualSize.width )
342       {
343         actualSize.width = lineRun.width;
344       }
345
346       actualSize.height += ( lineRun.ascender + -lineRun.descender );
347
348       // Traverse the glyphs and set the positions.
349
350       penY += layout.ascender;
351
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.
355       float penX = 0.f;
356
357       const GlyphInfo& glyph = *( layoutParameters.glyphsBuffer + index );
358       if( 0.f > glyph.xBearing )
359       {
360         penX = -glyph.xBearing;
361       }
362
363       for( GlyphIndex i = index; i < index + layout.numberOfGlyphs; ++i )
364       {
365         const GlyphInfo& glyph = *( layoutParameters.glyphsBuffer + i );
366         Vector2& position = *( glyphPositionsBuffer + i );
367
368         position.x = penX + glyph.xBearing;
369         position.y = penY - glyph.yBearing;
370
371         penX += glyph.advance;
372       }
373
374       penY += -layout.descender;
375
376       // Increase the glyph index.
377       index += layout.numberOfGlyphs;
378     }
379
380     return true;
381   }
382
383   void ReLayoutRightToLeftLines( const LayoutParameters& layoutParameters,
384                                  Vector<Vector2>& glyphPositions )
385   {
386     // Traverses the paragraphs with right to left characters.
387     for( LineIndex lineIndex = 0u; lineIndex < layoutParameters.numberOfBidirectionalInfoRuns; ++lineIndex )
388     {
389       const BidirectionalLineInfoRun& bidiLine = *( layoutParameters.lineBidirectionalInfoRunsBuffer + lineIndex );
390
391       float penX = 0.f;
392
393       const CharacterIndex characterVisualIndex = bidiLine.characterRun.characterIndex + *bidiLine.visualToLogicalMap;
394       const GlyphInfo& glyph = *( layoutParameters.glyphsBuffer + *( layoutParameters.charactersToGlyphsBuffer + characterVisualIndex ) );
395
396       penX = -glyph.xBearing;
397
398       Vector2* glyphPositionsBuffer = glyphPositions.Begin();
399
400       // Traverses the characters of the right to left paragraph.
401       for( CharacterIndex characterLogicalIndex = 0u;
402            characterLogicalIndex < bidiLine.characterRun.numberOfCharacters;
403            ++characterLogicalIndex )
404       {
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 );
407
408         // Get the number of glyphs of the character.
409         const Length numberOfGlyphs = *( layoutParameters.glyphsPerCharacterBuffer + characterVisualIndex );
410
411         for( GlyphIndex index = 0u; index < numberOfGlyphs; ++index )
412         {
413           // Convert the character in the visual order into the glyph in the visual order.
414           const GlyphIndex glyphIndex = *( layoutParameters.charactersToGlyphsBuffer + characterVisualIndex ) + index;
415
416           DALI_ASSERT_DEBUG( 0u <= glyphIndex && glyphIndex < layoutParameters.totalNumberOfGlyphs );
417
418           const GlyphInfo& glyph = *( layoutParameters.glyphsBuffer + glyphIndex );
419           Vector2& position = *( glyphPositionsBuffer + glyphIndex );
420
421           position.x = penX + glyph.xBearing;
422           penX += glyph.advance;
423         }
424       }
425     }
426   }
427
428   void Align( const LayoutParameters& layoutParameters,
429               const Size& layoutSize,
430               const Vector<LineRun>& lines,
431               Vector<Vector2>& glyphPositions )
432   {
433     Vector2* glyphPositionsBuffer = glyphPositions.Begin();
434
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.
438
439     LineIndex bidiLineIndex = 0u;
440     for( Vector<LineRun>::ConstIterator it = lines.Begin(), endIt = lines.End();
441          it != endIt;
442          ++it )
443     {
444       const LineRun& line = *it;
445
446       // 1) Get the paragrap's direction.
447       bool paragraphDirection = false;
448
449       // Check if there is any right to left line.
450       if( ( NULL != layoutParameters.lineBidirectionalInfoRunsBuffer ) &&
451           ( bidiLineIndex < layoutParameters.numberOfBidirectionalInfoRuns ) )
452       {
453         const BidirectionalLineInfoRun* bidiLine = layoutParameters.lineBidirectionalInfoRunsBuffer + bidiLineIndex;
454
455         // Get the right to left line that match with current line.
456         while( ( line.characterRun.characterIndex > bidiLine->characterRun.characterIndex ) &&
457                ( bidiLineIndex < layoutParameters.numberOfBidirectionalInfoRuns ) )
458         {
459           ++bidiLineIndex;
460           bidiLine = layoutParameters.lineBidirectionalInfoRunsBuffer + bidiLineIndex;
461         }
462
463         if( line.characterRun.characterIndex == bidiLine->characterRun.characterIndex )
464         {
465           paragraphDirection = bidiLine->direction;
466         }
467       }
468
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,
472                                                         line.width,
473                                                         line.extraLength,
474                                                         paragraphDirection );
475
476       // 3) Traverse all glyphs and update the 'x' position.
477       for( GlyphIndex index = line.glyphIndex,
478              endIndex = line.glyphIndex + line.numberOfGlyphs;
479            index < endIndex;
480            ++index )
481       {
482         Vector2& position = *( glyphPositionsBuffer + index );
483
484         position.x += alignOffset;
485       }
486     }
487   }
488
489   float CalculateHorizontalAlignment( float boxWidth,
490                                       float lineLength,
491                                       float extraLength,
492                                       bool paragraphDirection )
493   {
494     float offset = 0.f;
495
496     HorizontalAlignment alignment = mHorizontalAlignment;
497     if( paragraphDirection &&
498         ( HORIZONTAL_ALIGN_CENTER != alignment ) )
499     {
500       if( HORIZONTAL_ALIGN_BEGIN == alignment )
501       {
502         alignment = HORIZONTAL_ALIGN_END;
503       }
504       else
505       {
506         alignment = HORIZONTAL_ALIGN_BEGIN;
507       }
508     }
509
510     switch( alignment )
511     {
512       case HORIZONTAL_ALIGN_BEGIN:
513       {
514         offset = 0.f;
515         break;
516       }
517       case HORIZONTAL_ALIGN_CENTER:
518       {
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 );
522         break;
523       }
524       case HORIZONTAL_ALIGN_END:
525       {
526         offset = boxWidth - lineLength;
527         break;
528       }
529     }
530
531     if( paragraphDirection )
532     {
533       offset -= extraLength;
534     }
535
536     return offset;
537   }
538
539   LayoutEngine::Layout mLayout;
540   LayoutEngine::HorizontalAlignment mHorizontalAlignment;
541   LayoutEngine::VerticalAlignment mVerticalAlignment;
542
543   TextAbstraction::FontClient mFontClient;
544 };
545
546 LayoutEngine::LayoutEngine()
547 : mImpl( NULL )
548 {
549   mImpl = new LayoutEngine::Impl();
550 }
551
552 LayoutEngine::~LayoutEngine()
553 {
554   delete mImpl;
555 }
556
557 void LayoutEngine::SetLayout( Layout layout )
558 {
559   mImpl->mLayout = layout;
560 }
561
562 unsigned int LayoutEngine::GetLayout() const
563 {
564   return mImpl->mLayout;
565 }
566
567 void LayoutEngine::SetHorizontalAlignment( HorizontalAlignment alignment )
568 {
569   mImpl->mHorizontalAlignment = alignment;
570 }
571
572 LayoutEngine::HorizontalAlignment LayoutEngine::GetHorizontalAlignment() const
573 {
574   return mImpl->mHorizontalAlignment;
575 }
576
577 void LayoutEngine::SetVerticalAlignment( VerticalAlignment alignment )
578 {
579   mImpl->mVerticalAlignment = alignment;
580 }
581
582 LayoutEngine::VerticalAlignment LayoutEngine::GetVerticalAlignment() const
583 {
584   return mImpl->mVerticalAlignment;
585 }
586
587 bool LayoutEngine::LayoutText( const LayoutParameters& layoutParameters,
588                                Vector<Vector2>& glyphPositions,
589                                Vector<LineRun>& lines,
590                                Size& actualSize )
591 {
592   return mImpl->LayoutText( layoutParameters,
593                             glyphPositions,
594                             lines,
595                             actualSize );
596 }
597
598 void LayoutEngine::ReLayoutRightToLeftLines( const LayoutParameters& layoutParameters,
599                                              Vector<Vector2>& glyphPositions )
600 {
601   mImpl->ReLayoutRightToLeftLines( layoutParameters,
602                                    glyphPositions );
603 }
604
605 void LayoutEngine::Align( const LayoutParameters& layoutParameters,
606                           const Size& layoutSize,
607                           const Vector<LineRun>& lines,
608                           Vector<Vector2>& glyphPositions )
609 {
610   mImpl->Align( layoutParameters,
611                 layoutSize,
612                 lines,
613                 glyphPositions );
614 }
615
616 } // namespace Text
617
618 } // namespace Toolkit
619
620 } // namespace Dali