TextLayout - Use one method to do the single line or multi-line layout.
[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
44 } //namespace
45
46 /**
47  * @brief Stores temporary layout info of the line.
48  */
49 struct LineLayout
50 {
51   LineLayout()
52   : glyphIndex( 0u ),
53     characterIndex( 0u ),
54     numberOfCharacters( 0u ),
55     numberOfGlyphs( 0u ),
56     length( 0.f ),
57     widthAdvanceDiff( 0.f ),
58     wsLengthEndOfLine( 0.f ),
59     ascender( 0.f ),
60     descender( MAX_FLOAT )
61   {}
62
63   ~LineLayout()
64   {}
65
66   void Clear()
67   {
68     glyphIndex = 0u;
69     characterIndex = 0u;
70     numberOfCharacters = 0u;
71     numberOfGlyphs = 0u;
72     length = 0.f;
73     widthAdvanceDiff = 0.f;
74     wsLengthEndOfLine = 0.f;
75     ascender = 0.f;
76     descender = MAX_FLOAT;
77   }
78
79   GlyphIndex     glyphIndex;         ///< Index of the first glyph to be laid-out.
80   CharacterIndex characterIndex;     ///< Index of the first character to be laid-out.
81   Length         numberOfCharacters; ///< The number of characters which fit in one line.
82   Length         numberOfGlyphs;     ///< The number of glyph which fit in one line.
83   float          length;             ///< The length of the glyphs which fit in one line.
84   float          widthAdvanceDiff;   ///< The difference between the width and the advance of the last glyph.
85   float          wsLengthEndOfLine;  ///< The length of the white spaces at the end of the line.
86   float          ascender;           ///< The maximum ascender of all fonts in the line.
87   float          descender;          ///< The minimum descender of all fonts in the line.
88 };
89
90 struct LayoutEngine::Impl
91 {
92   Impl()
93   : mLayout( LayoutEngine::SINGLE_LINE_BOX ),
94     mHorizontalAlignment( LayoutEngine::HORIZONTAL_ALIGN_BEGIN ),
95     mVerticalAlignment( LayoutEngine::VERTICAL_ALIGN_TOP )
96   {
97     mFontClient = TextAbstraction::FontClient::Get();
98   }
99
100   /**
101    * @brief Merges a temporary line layout into the line layout.
102    *
103    * @param[in,out] lineLayout The line layout.
104    * @param[in] tmpLineLayout A temporary line layout.
105    */
106   void MergeLineLayout( LineLayout& lineLayout,
107                         const LineLayout& tmpLineLayout )
108   {
109     lineLayout.numberOfCharacters += tmpLineLayout.numberOfCharacters;
110     lineLayout.numberOfGlyphs += tmpLineLayout.numberOfGlyphs;
111     lineLayout.length += tmpLineLayout.length;
112     lineLayout.widthAdvanceDiff = tmpLineLayout.widthAdvanceDiff;
113
114     if( 0.f < tmpLineLayout.length )
115     {
116       lineLayout.length += lineLayout.wsLengthEndOfLine;
117
118       lineLayout.wsLengthEndOfLine = tmpLineLayout.wsLengthEndOfLine;
119     }
120     else
121     {
122       lineLayout.wsLengthEndOfLine += tmpLineLayout.wsLengthEndOfLine;
123     }
124
125     if( tmpLineLayout.ascender > lineLayout.ascender )
126     {
127       lineLayout.ascender = tmpLineLayout.ascender;
128     }
129
130     if( tmpLineLayout.descender < lineLayout.descender )
131     {
132       lineLayout.descender = tmpLineLayout.descender;
133     }
134   }
135
136   /**
137    * Retrieves the line layout for a given box width.
138    */
139   void GetLineLayoutForBox( const LayoutParameters& parameters,
140                             LineLayout& lineLayout )
141   {
142     // Stores temporary line layout which has not been added to the final line layout.
143     LineLayout tmpLineLayout;
144
145     const bool isMultiline = mLayout == MULTI_LINE_BOX;
146     const GlyphIndex lastGlyphIndex = parameters.totalNumberOfGlyphs - 1u;
147
148     FontId lastFontId = 0u;
149     for( GlyphIndex glyphIndex = lineLayout.glyphIndex;
150          glyphIndex < parameters.totalNumberOfGlyphs;
151          ++glyphIndex )
152     {
153       const bool isLastGlyph = glyphIndex == lastGlyphIndex;
154
155       // Get the glyph info.
156       const GlyphInfo& glyphInfo = *( parameters.glyphsBuffer + glyphIndex );
157
158       // Get the character indices for the current glyph. The last character index is needed
159       // because there are glyphs formed by more than one character but their break info is
160       // given only for the last character.
161       const Length charactersPerGlyph = *( parameters.charactersPerGlyphBuffer + glyphIndex );
162       const CharacterIndex characterFirstIndex = *( parameters.glyphsToCharactersBuffer + glyphIndex );
163       const CharacterIndex characterLastIndex = characterFirstIndex + ( ( 1u > charactersPerGlyph ) ? 0u : charactersPerGlyph - 1u );
164
165       // Get the line break info for the current character.
166       const LineBreakInfo lineBreakInfo = *( parameters.lineBreakInfoBuffer + characterLastIndex );
167
168       // Get the word break info for the current character.
169       const WordBreakInfo wordBreakInfo = *( parameters.wordBreakInfoBuffer + characterLastIndex );
170
171       // Increase the number of characters.
172       tmpLineLayout.numberOfCharacters += charactersPerGlyph;
173
174       // Increase the number of glyphs.
175       tmpLineLayout.numberOfGlyphs++;
176
177       // Check whether is a white space.
178       const Character character = *( parameters.textBuffer + characterFirstIndex );
179       const bool isWhiteSpace = TextAbstraction::IsWhiteSpace( character );
180
181       // Increase the accumulated length.
182       if( isWhiteSpace )
183       {
184         // Add the length to the length of white spaces at the end of the line.
185         tmpLineLayout.wsLengthEndOfLine += glyphInfo.advance; // I use the advance as the width is always zero for the white spaces.
186         tmpLineLayout.widthAdvanceDiff = 0.f;
187       }
188       else
189       {
190         // Add as well any previous white space length.
191         tmpLineLayout.length += tmpLineLayout.wsLengthEndOfLine + glyphInfo.advance;
192         tmpLineLayout.widthAdvanceDiff = glyphInfo.width - glyphInfo.advance;
193
194         // Clear the white space length at the end of the line.
195         tmpLineLayout.wsLengthEndOfLine = 0.f;
196       }
197
198       // Check if the accumulated length fits in the width of the box.
199       if( isMultiline &&
200           ( lineLayout.length + tmpLineLayout.length + tmpLineLayout.widthAdvanceDiff + ( ( 0.f < tmpLineLayout.length ) ? lineLayout.wsLengthEndOfLine : 0.f ) > parameters.boundingBox.width ) )
201       {
202         // Current word does not fit in the box's width.
203         return;
204       }
205
206       if( ( isMultiline || isLastGlyph ) &&
207           ( TextAbstraction::LINE_MUST_BREAK == lineBreakInfo ) )
208       {
209         // Must break the line. Update the line layout and return.
210         MergeLineLayout( lineLayout, tmpLineLayout );
211
212         return;
213       }
214
215       if( isMultiline &&
216           ( TextAbstraction::WORD_BREAK == wordBreakInfo ) )
217       {
218         // Current glyph is the last one of the current word.
219         // Add the temporal layout to the current one.
220         MergeLineLayout( lineLayout, tmpLineLayout );
221
222         tmpLineLayout.Clear();
223       }
224
225       if( lastFontId != glyphInfo.fontId )
226       {
227         Text::FontMetrics fontMetrics;
228         mFontClient.GetFontMetrics( glyphInfo.fontId, fontMetrics );
229
230         // Sets the maximum ascender.
231         if( fontMetrics.ascender > tmpLineLayout.ascender )
232         {
233           tmpLineLayout.ascender = fontMetrics.ascender;
234         }
235
236         // Sets the minimum descender.
237         if( -fontMetrics.descender < tmpLineLayout.descender )
238         {
239           tmpLineLayout.descender = fontMetrics.descender;
240         }
241
242         lastFontId = glyphInfo.fontId;
243       }
244     }
245   }
246
247   bool LayoutText( const LayoutParameters& layoutParameters,
248                    Vector<Vector2>& glyphPositions,
249                    Vector<LineRun>& lines,
250                    Size& actualSize )
251   {
252     float penY = 0.f;
253     for( GlyphIndex index = 0u; index < layoutParameters.totalNumberOfGlyphs; )
254     {
255       float penX = 0.f;
256
257       // Get the layout for the line.
258       LineLayout layout;
259       layout.glyphIndex = index;
260       GetLineLayoutForBox( layoutParameters,
261                            layout );
262
263       if( 0u == layout.numberOfGlyphs )
264       {
265         // The width is too small and no characters are laid-out.
266         return false;
267       }
268
269       // Create a line run and add it to the lines.
270       const GlyphIndex lastGlyphIndex = index + layout.numberOfGlyphs - 1u;
271
272       LineRun lineRun;
273       lineRun.glyphIndex = index;
274       lineRun.numberOfGlyphs = layout.numberOfGlyphs;
275       lineRun.characterRun.characterIndex = *( layoutParameters.glyphsToCharactersBuffer + index );
276       lineRun.characterRun.numberOfCharacters = ( *( layoutParameters.glyphsToCharactersBuffer + lastGlyphIndex ) + *( layoutParameters.charactersPerGlyphBuffer + lastGlyphIndex ) ) - lineRun.characterRun.characterIndex;
277       lineRun.width = layout.length + ( ( layout.widthAdvanceDiff > 0.f ) ? layout.widthAdvanceDiff : 0.f );
278       lineRun.ascender = layout.ascender;
279       lineRun.descender = layout.descender;
280       lineRun.extraLength = layout.wsLengthEndOfLine;
281       lineRun.direction = false;
282
283       lines.PushBack( lineRun );
284
285       // Update the actual size.
286       if( layout.length + layout.widthAdvanceDiff > actualSize.width )
287       {
288         actualSize.width = layout.length;
289       }
290
291       actualSize.height += ( lineRun.ascender + -lineRun.descender );
292
293       // Traverse the glyphs and set the positions.
294
295       penY += layout.ascender;
296
297       Vector2* glyphPositionsBuffer = glyphPositions.Begin();
298       for( GlyphIndex i = index; i < index + layout.numberOfGlyphs; ++i )
299       {
300         const GlyphInfo& glyph = *( layoutParameters.glyphsBuffer + i );
301         Vector2& position = *( glyphPositionsBuffer + i );
302
303         position.x = penX + glyph.xBearing;
304         position.y = penY - glyph.yBearing;
305
306         penX += glyph.advance;
307       }
308
309       penY += -layout.descender;
310
311       // Increase the glyph index.
312       index += layout.numberOfGlyphs;
313     }
314
315     return true;
316   }
317
318   void ReLayoutRightToLeftLines( const LayoutParameters& layoutParameters,
319                                  Vector<Vector2>& glyphPositions )
320   {
321     // Traverses the paragraphs with right to left characters.
322     for( LineIndex lineIndex = 0u; lineIndex < layoutParameters.numberOfBidirectionalInfoRuns; ++lineIndex )
323     {
324       const BidirectionalLineInfoRun& bidiLine = *( layoutParameters.lineBidirectionalInfoRunsBuffer + lineIndex );
325
326       float penX = 0.f;
327
328       Vector2* glyphPositionsBuffer = glyphPositions.Begin();
329
330       // Traverses the characters of the right to left paragraph.
331       for( CharacterIndex characterLogicalIndex = 0u;
332            characterLogicalIndex < bidiLine.characterRun.numberOfCharacters;
333            ++characterLogicalIndex )
334       {
335         // Convert the character in the logical order into the character in the visual order.
336         const CharacterIndex characterVisualIndex = bidiLine.characterRun.characterIndex + *( bidiLine.visualToLogicalMap + characterLogicalIndex );
337
338         // Get the number of glyphs of the character.
339         const Length numberOfGlyphs = *( layoutParameters.glyphsPerCharacterBuffer + characterVisualIndex );
340
341         for( GlyphIndex index = 0u; index < numberOfGlyphs; ++index )
342         {
343           // Convert the character in the visual order into the glyph in the visual order.
344           const GlyphIndex glyphIndex = *( layoutParameters.charactersToGlyphsBuffer + characterVisualIndex ) + index;
345
346           DALI_ASSERT_DEBUG( 0u <= glyphIndex && glyphIndex < layoutParameters.totalNumberOfGlyphs );
347
348           const GlyphInfo& glyph = *( layoutParameters.glyphsBuffer + glyphIndex );
349           Vector2& position = *( glyphPositionsBuffer + glyphIndex );
350
351           position.x = penX + glyph.xBearing;
352           penX += glyph.advance;
353         }
354       }
355     }
356   }
357
358   void Align( const LayoutParameters& layoutParameters,
359               const Size& layoutSize,
360               const Vector<LineRun>& lines,
361               Vector<Vector2>& glyphPositions )
362   {
363     Vector2* glyphPositionsBuffer = glyphPositions.Begin();
364
365     // Traverse all lines and align the glyphs.
366     // LayoutParameters contains bidirectional info for those lines with
367     // right to left text, this info includes the paragraph's direction.
368
369     LineIndex bidiLineIndex = 0u;
370     for( Vector<LineRun>::ConstIterator it = lines.Begin(), endIt = lines.End();
371          it != endIt;
372          ++it )
373     {
374       const LineRun& line = *it;
375
376       // 1) Get the paragrap's direction.
377       bool paragraphDirection = false;
378
379       // Check if there is any right to left line.
380       if( ( NULL != layoutParameters.lineBidirectionalInfoRunsBuffer ) &&
381           ( bidiLineIndex < layoutParameters.numberOfBidirectionalInfoRuns ) )
382       {
383         const BidirectionalLineInfoRun* bidiLine = layoutParameters.lineBidirectionalInfoRunsBuffer + bidiLineIndex;
384
385         // Get the right to left line that match with current line.
386         while( ( line.characterRun.characterIndex > bidiLine->characterRun.characterIndex ) &&
387                ( bidiLineIndex < layoutParameters.numberOfBidirectionalInfoRuns ) )
388         {
389           ++bidiLineIndex;
390           bidiLine = layoutParameters.lineBidirectionalInfoRunsBuffer + bidiLineIndex;
391         }
392
393         if( line.characterRun.characterIndex == bidiLine->characterRun.characterIndex )
394         {
395           paragraphDirection = bidiLine->direction;
396         }
397       }
398
399       // 2) Calculate the alignment offset accordingly with the align option,
400       //    the box width, line length, and the paragraphs direction.
401       float alignOffset = CalculateHorizontalAlignment( layoutSize.width,
402                                                         line.width,
403                                                         line.extraLength,
404                                                         paragraphDirection );
405
406       // 3) Traverse all glyphs and update the 'x' position.
407       for( GlyphIndex index = line.glyphIndex,
408              endIndex = line.glyphIndex + line.numberOfGlyphs;
409            index < endIndex;
410            ++index )
411       {
412         Vector2& position = *( glyphPositionsBuffer + index );
413
414         position.x += alignOffset;
415       }
416     }
417   }
418
419   float CalculateHorizontalAlignment( float boxWidth,
420                                       float lineLength,
421                                       float extraLength,
422                                       bool paragraphDirection )
423   {
424     float offset = 0.f;
425
426     HorizontalAlignment alignment = mHorizontalAlignment;
427     if( paragraphDirection &&
428         ( HORIZONTAL_ALIGN_CENTER != alignment ) )
429     {
430       if( HORIZONTAL_ALIGN_BEGIN == alignment )
431       {
432         alignment = HORIZONTAL_ALIGN_END;
433       }
434       else
435       {
436         alignment = HORIZONTAL_ALIGN_BEGIN;
437       }
438     }
439
440     switch( alignment )
441     {
442       case HORIZONTAL_ALIGN_BEGIN:
443       {
444         offset = 0.f;
445         break;
446       }
447       case HORIZONTAL_ALIGN_CENTER:
448       {
449         offset = 0.5f * ( boxWidth - lineLength );
450         const int intOffset = static_cast<int>( offset ); // try to avoid pixel alignment.
451         offset = static_cast<float>( intOffset );
452         break;
453       }
454       case HORIZONTAL_ALIGN_END:
455       {
456         offset = boxWidth - lineLength;
457         break;
458       }
459     }
460
461     if( paragraphDirection )
462     {
463       offset -= extraLength;
464     }
465
466     return offset;
467   }
468
469   LayoutEngine::Layout mLayout;
470   LayoutEngine::HorizontalAlignment mHorizontalAlignment;
471   LayoutEngine::VerticalAlignment mVerticalAlignment;
472
473   TextAbstraction::FontClient mFontClient;
474 };
475
476 LayoutEngine::LayoutEngine()
477 : mImpl( NULL )
478 {
479   mImpl = new LayoutEngine::Impl();
480 }
481
482 LayoutEngine::~LayoutEngine()
483 {
484   delete mImpl;
485 }
486
487 void LayoutEngine::SetLayout( Layout layout )
488 {
489   mImpl->mLayout = layout;
490 }
491
492 unsigned int LayoutEngine::GetLayout() const
493 {
494   return mImpl->mLayout;
495 }
496
497 void LayoutEngine::SetHorizontalAlignment( HorizontalAlignment alignment )
498 {
499   mImpl->mHorizontalAlignment = alignment;
500 }
501
502 LayoutEngine::HorizontalAlignment LayoutEngine::GetHorizontalAlignment() const
503 {
504   return mImpl->mHorizontalAlignment;
505 }
506
507 void LayoutEngine::SetVerticalAlignment( VerticalAlignment alignment )
508 {
509   mImpl->mVerticalAlignment = alignment;
510 }
511
512 LayoutEngine::VerticalAlignment LayoutEngine::GetVerticalAlignment() const
513 {
514   return mImpl->mVerticalAlignment;
515 }
516
517 bool LayoutEngine::LayoutText( const LayoutParameters& layoutParameters,
518                                Vector<Vector2>& glyphPositions,
519                                Vector<LineRun>& lines,
520                                Size& actualSize )
521 {
522   return mImpl->LayoutText( layoutParameters,
523                             glyphPositions,
524                             lines,
525                             actualSize );
526 }
527
528 void LayoutEngine::ReLayoutRightToLeftLines( const LayoutParameters& layoutParameters,
529                                              Vector<Vector2>& glyphPositions )
530 {
531   mImpl->ReLayoutRightToLeftLines( layoutParameters,
532                                    glyphPositions );
533 }
534
535 void LayoutEngine::Align( const LayoutParameters& layoutParameters,
536                           const Size& layoutSize,
537                           const Vector<LineRun>& lines,
538                           Vector<Vector2>& glyphPositions )
539 {
540   mImpl->Align( layoutParameters,
541                 layoutSize,
542                 lines,
543                 glyphPositions );
544 }
545
546 } // namespace Text
547
548 } // namespace Toolkit
549
550 } // namespace Dali