Merge "Layout Engine - Fix for multiline" into new_text
[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 <dali/public-api/math/vector2.h>
23 #include <dali/public-api/text-abstraction/font-client.h>
24
25 // INTERNAL INCLUDES
26 #include <dali-toolkit/internal/text/layouts/layout-parameters.h>
27 #include <dali-toolkit/internal/text/bidirectional-line-info-run.h>
28
29 namespace Dali
30 {
31
32 namespace Toolkit
33 {
34
35 namespace Text
36 {
37
38 /**
39  * @brief Stores temporary layout info of the line.
40  */
41 struct LineLayout
42 {
43   GlyphIndex     glyphIndex;         ///< Index of the first glyph to be laid-out.
44   CharacterIndex characterIndex;     ///< Index of the first character to be laid-out.
45   Length         numberOfCharacters; ///< The number of characters which fit in one line.
46   Length         numberOfGlyphs;     ///< The number of glyph which fit in one line.
47   float          length;             ///< The length of the glyphs which fit in one line.
48   float          wsLengthEndOfLine;  ///< The length of the white spaces at the end of the line.
49   float          height;             ///< The maximum height of all fonts in the line.
50   float          ascender;           ///< The maximum ascender of all fonts in the line.
51 };
52
53 struct LayoutEngine::Impl
54 {
55   Impl()
56   : mLayout( LayoutEngine::SINGLE_LINE_BOX ),
57     mAlignment( LayoutEngine::ALIGN_BEGIN )
58   {
59     mFontClient = TextAbstraction::FontClient::Get();
60   }
61
62   /**
63    * Retrieves the line layout for a given box width.
64    */
65   void GetLineLayoutForBox( const LayoutParameters& parameters,
66                             LineLayout& lineLayout )
67   {
68     // Initializes the line layout.
69     lineLayout.numberOfCharacters = 0u;
70     lineLayout.numberOfGlyphs = 0u;
71     lineLayout.length = 0.f;
72     lineLayout.wsLengthEndOfLine = 0.f;
73     lineLayout.height = 0.f;
74     lineLayout.ascender = 0.f;
75
76     // Get the last glyph index.
77     const GlyphIndex lastGlyphIndex = parameters.totalNumberOfGlyphs - 1u;
78
79     FontId lastFontId = 0u;
80     for( GlyphIndex glyphIndex = lineLayout.glyphIndex;
81          glyphIndex < parameters.totalNumberOfGlyphs;
82          ++glyphIndex )
83     {
84       // Get the glyph info.
85       const GlyphInfo& glyphInfo = *( parameters.glyphsBuffer + glyphIndex );
86
87       // Check whether is a white space.
88       const Character character = *( parameters.textBuffer + lineLayout.numberOfCharacters );
89       const bool isWhiteSpace = TextAbstraction::IsWhiteSpace( character );
90
91       // Get the character indices for the current glyph. The last character index is needed
92       // because there are glyphs formed by more than one character but their break info is
93       // given only for the last character.
94       const Length charactersPerGlyph = *( parameters.charactersPerGlyphBuffer + glyphIndex );
95
96       // Increase the number of characters.
97       lineLayout.numberOfCharacters += charactersPerGlyph;
98
99       // Increase the number of glyphs.
100       lineLayout.numberOfGlyphs++;
101
102       // Increase the accumulated length.
103       const float glyphLength = ( glyphIndex == lastGlyphIndex ) ? glyphInfo.width : glyphInfo.advance;
104
105       if( isWhiteSpace )
106       {
107         // Add the length to the length of white spaces at the end of the line.
108         lineLayout.wsLengthEndOfLine += glyphLength;
109       }
110       else
111       {
112         // Add as well any previous white space length.
113         lineLayout.length += lineLayout.wsLengthEndOfLine + glyphLength;
114
115         // Clear the white space length at the end of the line.
116         lineLayout.wsLengthEndOfLine = 0.f;
117       }
118
119       if( lastFontId != glyphInfo.fontId )
120       {
121         Text::FontMetrics fontMetrics;
122         mFontClient.GetFontMetrics( glyphInfo.fontId, fontMetrics );
123
124         // Sets the maximum height.
125         if( fontMetrics.height > lineLayout.height )
126         {
127           lineLayout.height = fontMetrics.height;
128         }
129
130         // Sets the maximum ascender.
131         if( fontMetrics.ascender > lineLayout.ascender )
132         {
133           lineLayout.ascender = fontMetrics.ascender;
134         }
135
136         lastFontId = glyphInfo.fontId;
137       }
138     }
139   }
140
141   /**
142    * Retrieves the line layout for a given box width.
143    */
144   void GetMultiLineLayoutForBox( const LayoutParameters& parameters,
145                                  LineLayout& lineLayout )
146   {
147     // Initializes the line layout.
148     lineLayout.numberOfCharacters = 0u;
149     lineLayout.numberOfGlyphs = 0u;
150     lineLayout.length = 0.f;
151     lineLayout.wsLengthEndOfLine = 0.f;
152     lineLayout.height = 0.f;
153     lineLayout.ascender = 0.f;
154
155     // Stores temporary line layout which has not been added to the final line layout.
156     LineLayout tmpLineLayout;
157     tmpLineLayout.numberOfCharacters = 0u;
158     tmpLineLayout.numberOfGlyphs = 0u;
159     tmpLineLayout.length = 0.f;
160     tmpLineLayout.wsLengthEndOfLine = 0.f;
161     tmpLineLayout.height = 0.f;
162     tmpLineLayout.ascender = 0.f;
163
164     // Get the last glyph index.
165     const GlyphIndex lastGlyphIndex = parameters.totalNumberOfGlyphs - 1u;
166
167     FontId lastFontId = 0u;
168     for( GlyphIndex glyphIndex = lineLayout.glyphIndex;
169          glyphIndex < parameters.totalNumberOfGlyphs;
170          ++glyphIndex )
171     {
172       // Get the glyph info.
173       const GlyphInfo& glyphInfo = *( parameters.glyphsBuffer + glyphIndex );
174
175       // Get the character indices for the current glyph. The last character index is needed
176       // because there are glyphs formed by more than one character but their break info is
177       // given only for the last character.
178       const Length charactersPerGlyph = *( parameters.charactersPerGlyphBuffer + glyphIndex );
179       const CharacterIndex characterFirstIndex = *( parameters.glyphsToCharactersBuffer + glyphIndex );
180       const CharacterIndex characterLastIndex = characterFirstIndex + ( ( 1u > charactersPerGlyph ) ? 0u : charactersPerGlyph - 1u );
181
182       // Get the line break info for the current character.
183       const LineBreakInfo lineBreakInfo = *( parameters.lineBreakInfoBuffer + characterLastIndex );
184
185       // Get the word break info for the current character.
186       const WordBreakInfo wordBreakInfo = *( parameters.wordBreakInfoBuffer + characterLastIndex );
187
188       // Increase the number of characters.
189       tmpLineLayout.numberOfCharacters += charactersPerGlyph;
190
191       // Increase the number of glyphs.
192       tmpLineLayout.numberOfGlyphs++;
193
194       // Check whether is a white space.
195       const Character character = *( parameters.textBuffer + characterFirstIndex );
196       const bool isWhiteSpace = TextAbstraction::IsWhiteSpace( character );
197
198       // Increase the accumulated length.
199       if( isWhiteSpace )
200       {
201         // Add the length to the length of white spaces at the end of the line.
202         tmpLineLayout.wsLengthEndOfLine += glyphInfo.advance; // I use the advance as the width is always zero for the white spaces.
203       }
204       else
205       {
206         // Add as well any previous white space length.
207         tmpLineLayout.length += tmpLineLayout.wsLengthEndOfLine + ( glyphIndex == lastGlyphIndex ) ? glyphInfo.width : glyphInfo.advance;
208
209         // Clear the white space length at the end of the line.
210         tmpLineLayout.wsLengthEndOfLine = 0.f;
211       }
212
213       // Check if the accumulated length fits in the width of the box.
214       if( lineLayout.length + tmpLineLayout.length + ( ( 0.f < tmpLineLayout.length ) ? lineLayout.wsLengthEndOfLine : 0.f ) > parameters.boundingBox.width )
215       {
216         // Current word does not fit in the box's width.
217         return;
218       }
219
220       if( TextAbstraction::LINE_MUST_BREAK == lineBreakInfo )
221       {
222         // Must break the line. Update the line layout and return.
223         lineLayout.numberOfCharacters += tmpLineLayout.numberOfCharacters;
224         lineLayout.numberOfGlyphs += tmpLineLayout.numberOfGlyphs;
225         lineLayout.length += tmpLineLayout.length;
226
227         if( 0.f < tmpLineLayout.length )
228         {
229           lineLayout.length += lineLayout.wsLengthEndOfLine;
230
231           lineLayout.wsLengthEndOfLine = tmpLineLayout.wsLengthEndOfLine;
232         }
233         else
234         {
235           lineLayout.wsLengthEndOfLine += tmpLineLayout.wsLengthEndOfLine;
236         }
237
238         if( tmpLineLayout.height > lineLayout.height )
239         {
240           lineLayout.height = tmpLineLayout.height;
241         }
242
243         if( tmpLineLayout.ascender > lineLayout.ascender )
244         {
245           lineLayout.ascender = tmpLineLayout.ascender;
246         }
247
248         tmpLineLayout.numberOfCharacters = 0u;
249         tmpLineLayout.numberOfGlyphs = 0u;
250         tmpLineLayout.length = 0u;
251         tmpLineLayout.wsLengthEndOfLine = 0u;
252         tmpLineLayout.height = 0.f;
253         tmpLineLayout.ascender = 0.f;
254         return;
255       }
256
257       if( TextAbstraction::WORD_BREAK == wordBreakInfo )
258       {
259         // Current glyph is the last one of the current word.
260         // Add the temporal layout to the current one.
261         lineLayout.numberOfCharacters += tmpLineLayout.numberOfCharacters;
262         lineLayout.numberOfGlyphs += tmpLineLayout.numberOfGlyphs;
263         lineLayout.length += tmpLineLayout.length;
264         if( 0.f < tmpLineLayout.length )
265         {
266           lineLayout.length += lineLayout.wsLengthEndOfLine;
267
268           lineLayout.wsLengthEndOfLine = tmpLineLayout.wsLengthEndOfLine;
269         }
270         else
271         {
272           lineLayout.wsLengthEndOfLine += tmpLineLayout.wsLengthEndOfLine;
273         }
274
275         if( tmpLineLayout.height > lineLayout.height )
276         {
277           lineLayout.height = tmpLineLayout.height;
278         }
279
280         if( tmpLineLayout.ascender > lineLayout.ascender )
281         {
282           lineLayout.ascender = tmpLineLayout.ascender;
283         }
284
285         tmpLineLayout.numberOfCharacters = 0u;
286         tmpLineLayout.numberOfGlyphs = 0u;
287         tmpLineLayout.length = 0u;
288         tmpLineLayout.wsLengthEndOfLine = 0u;
289         tmpLineLayout.height = 0.f;
290         tmpLineLayout.ascender = 0.f;
291       }
292
293       if( lastFontId != glyphInfo.fontId )
294       {
295         Text::FontMetrics fontMetrics;
296         mFontClient.GetFontMetrics( glyphInfo.fontId, fontMetrics );
297
298         // Sets the maximum height.
299         if( fontMetrics.height > tmpLineLayout.height )
300         {
301           tmpLineLayout.height = fontMetrics.height;
302         }
303
304         // Sets the maximum ascender.
305         if( fontMetrics.ascender > tmpLineLayout.ascender )
306         {
307           tmpLineLayout.ascender = fontMetrics.ascender;
308         }
309
310         lastFontId = glyphInfo.fontId;
311       }
312     }
313   }
314
315   bool LayoutText( const LayoutParameters& layoutParameters,
316                    Vector<Vector2>& glyphPositions,
317                    Vector<LineRun>& lines,
318                    Size& actualSize )
319   {
320     // TODO Switch between different layouts
321     bool update = false;
322
323     switch( mLayout )
324     {
325       case LayoutEngine::SINGLE_LINE_BOX:
326       {
327         update = SingleLineLayout( layoutParameters,
328                                    glyphPositions,
329                                    lines,
330                                    actualSize );
331         break;
332       }
333       case LayoutEngine::MULTI_LINE_BOX:
334       {
335         update = MultiLineLayout( layoutParameters,
336                                   glyphPositions,
337                                   lines,
338                                   actualSize );
339         break;
340       }
341       default:
342         break;
343     }
344
345     return update;
346   }
347
348   void ReLayoutRightToLeftLines( const LayoutParameters& layoutParameters,
349                                  Vector<Vector2>& glyphPositions )
350   {
351     for( LineIndex lineIndex = 0u; lineIndex < layoutParameters.numberOfBidirectionalInfoRuns; ++lineIndex )
352     {
353       const BidirectionalLineInfoRun& bidiLine = *( layoutParameters.lineBidirectionalInfoRunsBuffer +lineIndex  );
354
355       float penX = 0.f;
356
357       Vector2* glyphPositionsBuffer = glyphPositions.Begin();
358
359       for( CharacterIndex characterLogicalIndex = 0u;
360            characterLogicalIndex < bidiLine.characterRun.numberOfCharacters;
361            ++characterLogicalIndex )
362       {
363         // Convert the character in the logical order into the character in the visual order.
364         const CharacterIndex characterVisualIndex = bidiLine.characterRun.characterIndex + *( bidiLine.visualToLogicalMap + characterLogicalIndex );
365
366         // Get the number of glyphs of the character.
367         const Length numberOfGlyphs = *( layoutParameters.glyphsPerCharacterBuffer + characterVisualIndex );
368
369         for( GlyphIndex index = 0u; index < numberOfGlyphs; ++index )
370         {
371           // Convert the character in the visual order into the glyph in the visual order.
372           GlyphIndex glyphIndex = 1u + *( layoutParameters.charactersToGlyphsBuffer + characterVisualIndex + index ) - numberOfGlyphs;
373
374           const GlyphInfo& glyph = *( layoutParameters.glyphsBuffer + glyphIndex );
375           Vector2& position = *( glyphPositionsBuffer + glyphIndex );
376
377           position.x = penX + glyph.xBearing;
378           penX += glyph.advance;
379         }
380       }
381     }
382   }
383
384   void Align( const LayoutParameters& layoutParameters,
385               const Vector<LineRun>& lines,
386               Vector<Vector2>& glyphPositions )
387   {
388     Vector2* glyphPositionsBuffer = glyphPositions.Begin();
389
390     // Traverse all lines and align the glyphs.
391     // LayoutParameters contains bidirectional info for those lines with
392     // right to left text, this info includes the paragraph's direction.
393
394     LineIndex bidiLineIndex = 0u;
395     for( Vector<LineRun>::ConstIterator it = lines.Begin(), endIt = lines.End();
396          it != endIt;
397          ++it )
398     {
399       const LineRun& line = *it;
400
401       // 1) Get the paragrap's direction.
402       bool paragraphDirection = false;
403
404       // Check if there is any right to left line.
405       if( ( NULL != layoutParameters.lineBidirectionalInfoRunsBuffer ) &&
406           ( bidiLineIndex < layoutParameters.numberOfBidirectionalInfoRuns ) )
407       {
408         const BidirectionalLineInfoRun* bidiLine = layoutParameters.lineBidirectionalInfoRunsBuffer + bidiLineIndex;
409
410         // Get the right to left line that match with current line.
411         while( ( line.characterRun.characterIndex > bidiLine->characterRun.characterIndex ) &&
412                ( bidiLineIndex < layoutParameters.numberOfBidirectionalInfoRuns ) )
413         {
414           ++bidiLineIndex;
415           bidiLine = layoutParameters.lineBidirectionalInfoRunsBuffer + bidiLineIndex;
416         }
417
418         if( line.characterRun.characterIndex == bidiLine->characterRun.characterIndex )
419         {
420           paragraphDirection = bidiLine->direction;
421         }
422       }
423
424       // 2) Calculate the alignment offset accordingly with the align option,
425       //    the box width, line length, and the paragraphs direction.
426       float alignOffset = CalculateAlignment( layoutParameters.boundingBox.width,
427                                               line.lineSize.width,
428                                               line.extraLength,
429                                               paragraphDirection );
430
431       // 3) Traverse all glyphs and update the 'x' position.
432       for( GlyphIndex index = line.glyphIndex,
433              endIndex = line.glyphIndex + line.numberOfGlyphs;
434            index < endIndex;
435            ++index )
436       {
437         Vector2& position = *( glyphPositionsBuffer + index );
438
439         position.x += alignOffset;
440       }
441     }
442   }
443
444   bool SingleLineLayout( const LayoutParameters& layoutParameters,
445                          Vector<Vector2>& glyphPositions,
446                          Vector<LineRun>& lines,
447                          Size& actualSize )
448   {
449     LineLayout layout;
450     layout.glyphIndex = 0u;
451     GetLineLayoutForBox( layoutParameters,
452                          layout );
453
454     // Create a line run and add it to the lines.
455     const GlyphIndex lastGlyphIndex = layoutParameters.totalNumberOfGlyphs - 1u;
456
457     LineRun lineRun;
458     lineRun.glyphIndex = 0u;
459     lineRun.numberOfGlyphs = layoutParameters.totalNumberOfGlyphs;
460     lineRun.characterRun.characterIndex = 0u;
461     lineRun.characterRun.numberOfCharacters = *( layoutParameters.glyphsToCharactersBuffer + lastGlyphIndex ) + *( layoutParameters.charactersPerGlyphBuffer + lastGlyphIndex );
462     lineRun.lineSize.width = layout.length;
463     lineRun.lineSize.height = layout.height;
464     lineRun.extraLength = layout.wsLengthEndOfLine;
465
466     lines.PushBack( lineRun );
467
468     // Update the actual size.
469     actualSize.width = layout.length;
470     actualSize.height = layout.height;
471
472     float penX = 0.f;
473     float penY = layout.height;
474
475     Vector2* glyphPositionsBuffer = glyphPositions.Begin();
476     for( GlyphIndex glyphIndex = 0u; glyphIndex < layout.numberOfGlyphs; ++glyphIndex )
477     {
478       const GlyphInfo& glyph = *( layoutParameters.glyphsBuffer + glyphIndex );
479       Vector2& position = *( glyphPositionsBuffer + glyphIndex );
480
481       position.x = penX + glyph.xBearing;
482       position.y = penY - glyph.yBearing;
483
484       penX += glyph.advance;
485     }
486
487     return true;
488   }
489
490   bool MultiLineLayout( const LayoutParameters& layoutParameters,
491                         Vector<Vector2>& glyphPositions,
492                         Vector<LineRun>& lines,
493                         Size& actualSize )
494   {
495     float penY = 0.f;
496     for( GlyphIndex index = 0u; index < layoutParameters.totalNumberOfGlyphs; )
497     {
498       float penX = 0.f;
499
500       // Get the layout for the line.
501       LineLayout layout;
502       layout.glyphIndex = index;
503       GetMultiLineLayoutForBox( layoutParameters,
504                                 layout );
505
506       if( 0u == layout.numberOfGlyphs )
507       {
508         // The width is too small and no characters are laid-out.
509         return false;
510       }
511
512       // Create a line run and add it to the lines.
513       const GlyphIndex lastGlyphIndex = index + layout.numberOfGlyphs - 1u;
514
515       LineRun lineRun;
516       lineRun.glyphIndex = index;
517       lineRun.numberOfGlyphs = layout.numberOfGlyphs;
518       lineRun.characterRun.characterIndex = *( layoutParameters.glyphsToCharactersBuffer + index );
519       lineRun.characterRun.numberOfCharacters = ( *( layoutParameters.glyphsToCharactersBuffer + lastGlyphIndex ) + *( layoutParameters.charactersPerGlyphBuffer + lastGlyphIndex ) ) - lineRun.characterRun.characterIndex;
520       lineRun.lineSize.width = layout.length;
521       lineRun.lineSize.height = layout.height;
522       lineRun.extraLength = layout.wsLengthEndOfLine;
523
524       lines.PushBack( lineRun );
525
526       // Update the actual size.
527       if( layout.length > actualSize.width )
528       {
529         actualSize.width = layout.length;
530       }
531
532       actualSize.height += layout.height;
533
534       // Traverse the glyphs and set the positions.
535
536       penY += layout.height;
537
538       Vector2* glyphPositionsBuffer = glyphPositions.Begin();
539       for( GlyphIndex i = index; i < index + layout.numberOfGlyphs; ++i )
540       {
541         const GlyphInfo& glyph = *( layoutParameters.glyphsBuffer + i );
542         Vector2& position = *( glyphPositionsBuffer + i );
543
544         position.x = penX + glyph.xBearing;
545         position.y = penY - glyph.yBearing;
546
547         penX += glyph.advance;
548       }
549
550       // Increase the glyph index.
551       index += layout.numberOfGlyphs;
552     }
553
554     return true;
555   }
556
557   float CalculateAlignment( float boxWidth,
558                             float lineLength,
559                             float extraLength,
560                             bool paragraphDirection )
561   {
562     float offset = 0.f;
563
564     Alignment alignment = mAlignment;
565     if( paragraphDirection &&
566         ( ALIGN_CENTER != alignment ) )
567     {
568       if( ALIGN_BEGIN == alignment )
569       {
570         alignment = ALIGN_END;
571       }
572       else
573       {
574         alignment = ALIGN_BEGIN;
575       }
576     }
577
578     switch( alignment )
579     {
580       case ALIGN_BEGIN:
581       {
582         offset = 0.f;
583         break;
584       }
585       case ALIGN_CENTER:
586       {
587         offset = 0.5f * ( boxWidth - lineLength );
588         const int intOffset = static_cast<int>( offset ); // try to avoid pixel alignment.
589         offset = static_cast<float>( intOffset );
590         break;
591       }
592       case ALIGN_END:
593       {
594         offset = boxWidth - lineLength;
595         break;
596       }
597     }
598
599     if( paragraphDirection )
600     {
601       offset -= extraLength;
602     }
603
604     return offset;
605   }
606
607   LayoutEngine::Layout mLayout;
608   LayoutEngine::Alignment mAlignment;
609
610   TextAbstraction::FontClient mFontClient;
611 };
612
613 LayoutEngine::LayoutEngine()
614 : mImpl( NULL )
615 {
616   mImpl = new LayoutEngine::Impl();
617 }
618
619 LayoutEngine::~LayoutEngine()
620 {
621   delete mImpl;
622 }
623
624 void LayoutEngine::SetLayout( Layout layout )
625 {
626   mImpl->mLayout = layout;
627 }
628
629 unsigned int LayoutEngine::GetLayout() const
630 {
631   return mImpl->mLayout;
632 }
633
634 void LayoutEngine::SetAlignment( Alignment alignment )
635 {
636   mImpl->mAlignment = alignment;
637 }
638
639 LayoutEngine::Alignment LayoutEngine::GetAlignment() const
640 {
641   return mImpl->mAlignment;
642 }
643
644 bool LayoutEngine::LayoutText( const LayoutParameters& layoutParameters,
645                                Vector<Vector2>& glyphPositions,
646                                Vector<LineRun>& lines,
647                                Size& actualSize )
648 {
649   return mImpl->LayoutText( layoutParameters,
650                             glyphPositions,
651                             lines,
652                             actualSize );
653 }
654
655 void LayoutEngine::ReLayoutRightToLeftLines( const LayoutParameters& layoutParameters,
656                                              Vector<Vector2>& glyphPositions )
657 {
658   mImpl->ReLayoutRightToLeftLines( layoutParameters,
659                                    glyphPositions );
660 }
661
662 void LayoutEngine::Align( const LayoutParameters& layoutParameters,
663                           const Vector<LineRun>& lines,
664                           Vector<Vector2>& glyphPositions )
665 {
666   mImpl->Align( layoutParameters,
667                 lines,
668                 glyphPositions );
669 }
670
671 } // namespace Text
672
673 } // namespace Toolkit
674
675 } // namespace Dali