Text Alignment
[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         if( glyphIndex == lastGlyphIndex )
223         {
224           // Must break the line. Update the line layout and return.
225           lineLayout.numberOfCharacters += tmpLineLayout.numberOfCharacters;
226           lineLayout.numberOfGlyphs += tmpLineLayout.numberOfGlyphs;
227           lineLayout.length += tmpLineLayout.length;
228
229           if( 0.f < tmpLineLayout.length )
230           {
231             lineLayout.length += lineLayout.wsLengthEndOfLine;
232
233             lineLayout.wsLengthEndOfLine = tmpLineLayout.wsLengthEndOfLine;
234           }
235           else
236           {
237             lineLayout.wsLengthEndOfLine += tmpLineLayout.wsLengthEndOfLine;
238           }
239
240           if( tmpLineLayout.height > lineLayout.height )
241           {
242             lineLayout.height = tmpLineLayout.height;
243           }
244
245           if( tmpLineLayout.ascender > lineLayout.ascender )
246           {
247             lineLayout.ascender = tmpLineLayout.ascender;
248           }
249         }
250
251         tmpLineLayout.numberOfCharacters = 0u;
252         tmpLineLayout.numberOfGlyphs = 0u;
253         tmpLineLayout.length = 0u;
254         tmpLineLayout.wsLengthEndOfLine = 0u;
255         tmpLineLayout.height = 0.f;
256         tmpLineLayout.ascender = 0.f;
257         return;
258       }
259
260       if( TextAbstraction::WORD_BREAK == wordBreakInfo )
261       {
262         // Current glyph is the last one of the current word.
263         // Add the temporal layout to the current one.
264         lineLayout.numberOfCharacters += tmpLineLayout.numberOfCharacters;
265         lineLayout.numberOfGlyphs += tmpLineLayout.numberOfGlyphs;
266         lineLayout.length += tmpLineLayout.length;
267         if( 0.f < tmpLineLayout.length )
268         {
269           lineLayout.length += lineLayout.wsLengthEndOfLine;
270
271           lineLayout.wsLengthEndOfLine = tmpLineLayout.wsLengthEndOfLine;
272         }
273         else
274         {
275           lineLayout.wsLengthEndOfLine += tmpLineLayout.wsLengthEndOfLine;
276         }
277
278         if( tmpLineLayout.height > lineLayout.height )
279         {
280           lineLayout.height = tmpLineLayout.height;
281         }
282
283         if( tmpLineLayout.ascender > lineLayout.ascender )
284         {
285           lineLayout.ascender = tmpLineLayout.ascender;
286         }
287
288         tmpLineLayout.numberOfCharacters = 0u;
289         tmpLineLayout.numberOfGlyphs = 0u;
290         tmpLineLayout.length = 0u;
291         tmpLineLayout.wsLengthEndOfLine = 0u;
292         tmpLineLayout.height = 0.f;
293         tmpLineLayout.ascender = 0.f;
294       }
295
296       if( lastFontId != glyphInfo.fontId )
297       {
298         Text::FontMetrics fontMetrics;
299         mFontClient.GetFontMetrics( glyphInfo.fontId, fontMetrics );
300
301         // Sets the maximum height.
302         if( fontMetrics.height > tmpLineLayout.height )
303         {
304           tmpLineLayout.height = fontMetrics.height;
305         }
306
307         // Sets the maximum ascender.
308         if( fontMetrics.ascender > tmpLineLayout.ascender )
309         {
310           tmpLineLayout.ascender = fontMetrics.ascender;
311         }
312
313         lastFontId = glyphInfo.fontId;
314       }
315     }
316   }
317
318   bool LayoutText( const LayoutParameters& layoutParameters,
319                    Vector<Vector2>& glyphPositions,
320                    Vector<LineRun>& lines,
321                    Size& actualSize )
322   {
323     // TODO Switch between different layouts
324     bool update = false;
325
326     switch( mLayout )
327     {
328       case LayoutEngine::SINGLE_LINE_BOX:
329       {
330         update = SingleLineLayout( layoutParameters,
331                                    glyphPositions,
332                                    lines,
333                                    actualSize );
334         break;
335       }
336       case LayoutEngine::MULTI_LINE_BOX:
337       {
338         update = MultiLineLayout( layoutParameters,
339                                   glyphPositions,
340                                   lines,
341                                   actualSize );
342         break;
343       }
344       default:
345         break;
346     }
347
348     return update;
349   }
350
351   void ReLayoutRightToLeftLines( const LayoutParameters& layoutParameters,
352                                  Vector<Vector2>& glyphPositions )
353   {
354     for( LineIndex lineIndex = 0u; lineIndex < layoutParameters.numberOfBidirectionalInfoRuns; ++lineIndex )
355     {
356       const BidirectionalLineInfoRun& bidiLine = *( layoutParameters.lineBidirectionalInfoRunsBuffer +lineIndex  );
357
358       float penX = 0.f;
359
360       Vector2* glyphPositionsBuffer = glyphPositions.Begin();
361
362       for( CharacterIndex characterLogicalIndex = 0u;
363            characterLogicalIndex < bidiLine.characterRun.numberOfCharacters;
364            ++characterLogicalIndex )
365       {
366         // Convert the character in the logical order into the character in the visual order.
367         const CharacterIndex characterVisualIndex = bidiLine.characterRun.characterIndex + *( bidiLine.visualToLogicalMap + characterLogicalIndex );
368
369         // Get the number of glyphs of the character.
370         const Length numberOfGlyphs = *( layoutParameters.glyphsPerCharacterBuffer + characterVisualIndex );
371
372         for( GlyphIndex index = 0u; index < numberOfGlyphs; ++index )
373         {
374           // Convert the character in the visual order into the glyph in the visual order.
375           GlyphIndex glyphIndex = 1u + *( layoutParameters.charactersToGlyphsBuffer + characterVisualIndex + index ) - numberOfGlyphs;
376
377           const GlyphInfo& glyph = *( layoutParameters.glyphsBuffer + glyphIndex );
378           Vector2& position = *( glyphPositionsBuffer + glyphIndex );
379
380           position.x = penX + glyph.xBearing;
381           penX += glyph.advance;
382         }
383       }
384     }
385   }
386
387   void Align( const LayoutParameters& layoutParameters,
388               const Vector<LineRun>& lines,
389               Vector<Vector2>& glyphPositions )
390   {
391     Vector2* glyphPositionsBuffer = glyphPositions.Begin();
392
393     // Traverse all lines and align the glyphs.
394     // LayoutParameters contains bidirectional info for those lines with
395     // right to left text, this info includes the paragraph's direction.
396
397     LineIndex bidiLineIndex = 0u;
398     for( Vector<LineRun>::ConstIterator it = lines.Begin(), endIt = lines.End();
399          it != endIt;
400          ++it )
401     {
402       const LineRun& line = *it;
403
404       // 1) Get the paragrap's direction.
405       bool paragraphDirection = false;
406
407       // Check if there is any right to left line.
408       if( ( NULL != layoutParameters.lineBidirectionalInfoRunsBuffer ) &&
409           ( bidiLineIndex < layoutParameters.numberOfBidirectionalInfoRuns ) )
410       {
411         const BidirectionalLineInfoRun* bidiLine = layoutParameters.lineBidirectionalInfoRunsBuffer + bidiLineIndex;
412
413         // Get the right to left line that match with current line.
414         while( ( line.characterRun.characterIndex > bidiLine->characterRun.characterIndex ) &&
415                ( bidiLineIndex < layoutParameters.numberOfBidirectionalInfoRuns ) )
416         {
417           ++bidiLineIndex;
418           bidiLine = layoutParameters.lineBidirectionalInfoRunsBuffer + bidiLineIndex;
419         }
420
421         if( line.characterRun.characterIndex == bidiLine->characterRun.characterIndex )
422         {
423           paragraphDirection = bidiLine->direction;
424         }
425       }
426
427       // 2) Calculate the alignment offset accordingly with the align option,
428       //    the box width, line length, and the paragraphs direction.
429       float alignOffset = CalculateAlignment( layoutParameters.boundingBox.width,
430                                               line.lineSize.width,
431                                               line.extraLength,
432                                               paragraphDirection );
433
434       // 3) Traverse all glyphs and update the 'x' position.
435       for( GlyphIndex index = line.glyphIndex,
436              endIndex = line.glyphIndex + line.numberOfGlyphs;
437            index < endIndex;
438            ++index )
439       {
440         Vector2& position = *( glyphPositionsBuffer + index );
441
442         position.x += alignOffset;
443       }
444     }
445   }
446
447   bool SingleLineLayout( const LayoutParameters& layoutParameters,
448                          Vector<Vector2>& glyphPositions,
449                          Vector<LineRun>& lines,
450                          Size& actualSize )
451   {
452     LineLayout layout;
453     layout.glyphIndex = 0u;
454     GetLineLayoutForBox( layoutParameters,
455                          layout );
456
457     // Create a line run and add it to the lines.
458     const GlyphIndex lastGlyphIndex = layoutParameters.totalNumberOfGlyphs - 1u;
459
460     LineRun lineRun;
461     lineRun.glyphIndex = 0u;
462     lineRun.numberOfGlyphs = layoutParameters.totalNumberOfGlyphs;
463     lineRun.characterRun.characterIndex = 0u;
464     lineRun.characterRun.numberOfCharacters = *( layoutParameters.glyphsToCharactersBuffer + lastGlyphIndex ) + *( layoutParameters.charactersPerGlyphBuffer + lastGlyphIndex );
465     lineRun.lineSize.width = layout.length;
466     lineRun.lineSize.height = layout.height;
467     lineRun.extraLength = layout.wsLengthEndOfLine;
468
469     lines.PushBack( lineRun );
470
471     // Update the actual size.
472     actualSize.width = layout.length;
473     actualSize.height = layout.height;
474
475     float penX = 0.f;
476     float penY = layout.height;
477
478     Vector2* glyphPositionsBuffer = glyphPositions.Begin();
479     for( GlyphIndex glyphIndex = 0u; glyphIndex < layout.numberOfGlyphs; ++glyphIndex )
480     {
481       const GlyphInfo& glyph = *( layoutParameters.glyphsBuffer + glyphIndex );
482       Vector2& position = *( glyphPositionsBuffer + glyphIndex );
483
484       position.x = penX + glyph.xBearing;
485       position.y = penY - glyph.yBearing;
486
487       penX += glyph.advance;
488     }
489
490     return true;
491   }
492
493   bool MultiLineLayout( const LayoutParameters& layoutParameters,
494                         Vector<Vector2>& glyphPositions,
495                         Vector<LineRun>& lines,
496                         Size& actualSize )
497   {
498     float penY = 0.f;
499     for( GlyphIndex index = 0u; index < layoutParameters.totalNumberOfGlyphs; )
500     {
501       float penX = 0.f;
502
503       // Get the layout for the line.
504       LineLayout layout;
505       layout.glyphIndex = index;
506       GetMultiLineLayoutForBox( layoutParameters,
507                                 layout );
508
509       if( 0u == layout.numberOfGlyphs )
510       {
511         // The width is too small and no characters are laid-out.
512         return false;
513       }
514
515       // Create a line run and add it to the lines.
516       const GlyphIndex lastGlyphIndex = index + layout.numberOfGlyphs - 1u;
517
518       LineRun lineRun;
519       lineRun.glyphIndex = index;
520       lineRun.numberOfGlyphs = layout.numberOfGlyphs;
521       lineRun.characterRun.characterIndex = *( layoutParameters.glyphsToCharactersBuffer + index );
522       lineRun.characterRun.numberOfCharacters = ( *( layoutParameters.glyphsToCharactersBuffer + lastGlyphIndex ) + *( layoutParameters.charactersPerGlyphBuffer + lastGlyphIndex ) ) - lineRun.characterRun.characterIndex;
523       lineRun.lineSize.width = layout.length;
524       lineRun.lineSize.height = layout.height;
525       lineRun.extraLength = layout.wsLengthEndOfLine;
526
527       lines.PushBack( lineRun );
528
529       // Update the actual size.
530       if( layout.length > actualSize.width )
531       {
532         actualSize.width = layout.length;
533       }
534
535       actualSize.height += layout.height;
536
537       // Traverse the glyphs and set the positions.
538
539       penY += layout.height;
540
541       Vector2* glyphPositionsBuffer = glyphPositions.Begin();
542       for( GlyphIndex i = index; i < index + layout.numberOfGlyphs; ++i )
543       {
544         const GlyphInfo& glyph = *( layoutParameters.glyphsBuffer + i );
545         Vector2& position = *( glyphPositionsBuffer + i );
546
547         position.x = penX + glyph.xBearing;
548         position.y = penY - glyph.yBearing;
549
550         penX += glyph.advance;
551       }
552
553       // Increase the glyph index.
554       index += layout.numberOfGlyphs;
555     }
556
557     return true;
558   }
559
560   float CalculateAlignment( float boxWidth,
561                             float lineLength,
562                             float extraLength,
563                             bool paragraphDirection )
564   {
565     float offset = 0.f;
566
567     Alignment alignment = mAlignment;
568     if( paragraphDirection &&
569         ( ALIGN_CENTER != alignment ) )
570     {
571       if( ALIGN_BEGIN == alignment )
572       {
573         alignment = ALIGN_END;
574       }
575       else
576       {
577         alignment = ALIGN_BEGIN;
578       }
579     }
580
581     switch( alignment )
582     {
583       case ALIGN_BEGIN:
584       {
585         offset = 0.f;
586         break;
587       }
588       case ALIGN_CENTER:
589       {
590         offset = 0.5f * ( boxWidth - lineLength );
591         break;
592       }
593       case ALIGN_END:
594       {
595         offset = boxWidth - lineLength;
596         break;
597       }
598     }
599
600     if( paragraphDirection )
601     {
602       offset -= extraLength;
603     }
604
605     return offset;
606   }
607
608   LayoutEngine::Layout mLayout;
609   LayoutEngine::Alignment mAlignment;
610
611   TextAbstraction::FontClient mFontClient;
612 };
613
614 LayoutEngine::LayoutEngine()
615 : mImpl( NULL )
616 {
617   mImpl = new LayoutEngine::Impl();
618 }
619
620 LayoutEngine::~LayoutEngine()
621 {
622   delete mImpl;
623 }
624
625 void LayoutEngine::SetLayout( Layout layout )
626 {
627   mImpl->mLayout = layout;
628 }
629
630 unsigned int LayoutEngine::GetLayout() const
631 {
632   return mImpl->mLayout;
633 }
634
635 void LayoutEngine::SetAlignment( Alignment alignment )
636 {
637   mImpl->mAlignment = alignment;
638 }
639
640 LayoutEngine::Alignment LayoutEngine::GetAlignment() const
641 {
642   return mImpl->mAlignment;
643 }
644
645 bool LayoutEngine::LayoutText( const LayoutParameters& layoutParameters,
646                                Vector<Vector2>& glyphPositions,
647                                Vector<LineRun>& lines,
648                                Size& actualSize )
649 {
650   return mImpl->LayoutText( layoutParameters,
651                             glyphPositions,
652                             lines,
653                             actualSize );
654 }
655
656 void LayoutEngine::ReLayoutRightToLeftLines( const LayoutParameters& layoutParameters,
657                                              Vector<Vector2>& glyphPositions )
658 {
659   mImpl->ReLayoutRightToLeftLines( layoutParameters,
660                                    glyphPositions );
661 }
662
663 void LayoutEngine::Align( const LayoutParameters& layoutParameters,
664                           const Vector<LineRun>& lines,
665                           Vector<Vector2>& glyphPositions )
666 {
667   mImpl->Align( layoutParameters,
668                 lines,
669                 glyphPositions );
670 }
671
672 } // namespace Text
673
674 } // namespace Toolkit
675
676 } // namespace Dali