0b9e39daa7a50702ca76c50391fdd871d178b6b1
[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/integration-api/debug.h>
24 #include <dali/devel-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 #if defined(DEBUG_ENABLED)
43   Debug::Filter* gLogFilter = Debug::Filter::New(Debug::Concise, true, "LOG_TEXT_LAYOUT");
44 #endif
45
46 const float MAX_FLOAT = std::numeric_limits<float>::max();
47 const bool RTL = true;
48 const float CURSOR_WIDTH = 1.f;
49
50 } //namespace
51
52 /**
53  * @brief Stores temporary layout info of the line.
54  */
55 struct LineLayout
56 {
57   LineLayout()
58   : glyphIndex( 0u ),
59     characterIndex( 0u ),
60     numberOfGlyphs( 0u ),
61     numberOfCharacters( 0u ),
62     length( 0.f ),
63     extraBearing( 0.f ),
64     extraWidth( 0.f ),
65     wsLengthEndOfLine( 0.f ),
66     ascender( 0.f ),
67     descender( MAX_FLOAT )
68   {}
69
70   ~LineLayout()
71   {}
72
73   void Clear()
74   {
75     glyphIndex = 0u;
76     characterIndex = 0u;
77     numberOfGlyphs = 0u;
78     numberOfCharacters = 0u;
79     length = 0.f;
80     extraBearing = 0.f;
81     extraWidth = 0.f;
82     wsLengthEndOfLine = 0.f;
83     ascender = 0.f;
84     descender = MAX_FLOAT;
85   }
86
87   GlyphIndex     glyphIndex;         ///< Index of the first glyph to be laid-out.
88   CharacterIndex characterIndex;     ///< Index of the first character to be laid-out.
89   Length         numberOfGlyphs;     ///< The number of glyph which fit in one line.
90   Length         numberOfCharacters; ///< The number of characters which fit in one line.
91   float          length;             ///< The addition of the advance metric of all the glyphs which fit in one line.
92   float          extraBearing;       ///< The extra width to be added to the line's length when the bearing of the first glyph is negative.
93   float          extraWidth;         ///< The extra width to be added to the line's length when the bearing + width of the last glyph is greater than the advance.
94   float          wsLengthEndOfLine;  ///< The length of the white spaces at the end of the line.
95   float          ascender;           ///< The maximum ascender of all fonts in the line.
96   float          descender;          ///< The minimum descender of all fonts in the line.
97 };
98
99 struct LayoutEngine::Impl
100 {
101   Impl()
102   : mLayout( LayoutEngine::SINGLE_LINE_BOX ),
103     mHorizontalAlignment( LayoutEngine::HORIZONTAL_ALIGN_BEGIN ),
104     mVerticalAlignment( LayoutEngine::VERTICAL_ALIGN_TOP ),
105     mCursorWidth( CURSOR_WIDTH ),
106     mEllipsisEnabled( false )
107   {
108   }
109
110   /**
111    * @brief Updates the line ascender and descender with the metrics of a new font.
112    *
113    * @param[in] fontId The id of the new font.
114    * @param[in,out] lineLayout The line layout.
115    */
116   void UpdateLineHeight( FontId fontId, LineLayout& lineLayout )
117   {
118     Text::FontMetrics fontMetrics;
119     mMetrics->GetFontMetrics( fontId, fontMetrics );
120
121     // Sets the maximum ascender.
122     if( fontMetrics.ascender > lineLayout.ascender )
123     {
124       lineLayout.ascender = fontMetrics.ascender;
125     }
126
127     // Sets the minimum descender.
128     if( fontMetrics.descender < lineLayout.descender )
129     {
130       lineLayout.descender = fontMetrics.descender;
131     }
132   }
133
134   /**
135    * @brief Merges a temporary line layout into the line layout.
136    *
137    * @param[in,out] lineLayout The line layout.
138    * @param[in] tmpLineLayout A temporary line layout.
139    */
140   void MergeLineLayout( LineLayout& lineLayout,
141                         const LineLayout& tmpLineLayout )
142   {
143     lineLayout.numberOfCharacters += tmpLineLayout.numberOfCharacters;
144     lineLayout.numberOfGlyphs += tmpLineLayout.numberOfGlyphs;
145     lineLayout.length += tmpLineLayout.length;
146
147     if( 0.f < tmpLineLayout.length )
148     {
149       lineLayout.length += lineLayout.wsLengthEndOfLine;
150
151       lineLayout.wsLengthEndOfLine = tmpLineLayout.wsLengthEndOfLine;
152     }
153     else
154     {
155       lineLayout.wsLengthEndOfLine += tmpLineLayout.wsLengthEndOfLine;
156     }
157
158     if( tmpLineLayout.ascender > lineLayout.ascender )
159     {
160       lineLayout.ascender = tmpLineLayout.ascender;
161     }
162
163     if( tmpLineLayout.descender < lineLayout.descender )
164     {
165       lineLayout.descender = tmpLineLayout.descender;
166     }
167   }
168
169   /**
170    * Retrieves the line layout for a given box width.
171    *
172    * @note This method lais out text as it were left to right. At this point is not possible to reorder the line
173    *       because the number of characters of the line is not known (one of the responsabilities of this method
174    *       is calculate that). Due to glyph's 'x' bearing, width and advance, when right to left or mixed right to left
175    *       and left to right text is laid-out, it can be small differences in the line length. One solution is to
176    *       reorder and re-lay out the text after this method and add or remove one extra glyph if needed. However,
177    *       this method calculates which are the first and last glyphs of the line (the ones that causes the
178    *       differences). This is a good point to check if there is problems with the text exceeding the boundaries
179    *       of the control when there is right to left text.
180    *
181    * @param[in] parameters The layout parameters.
182    * @param[out] lineLayout The line layout.
183    * @param[in,out] paragraphDirection in: the current paragraph's direction, out: the next paragraph's direction. Is set after a must break.
184    * @param[in] completelyFill Whether to completely fill the line ( even if the last word exceeds the boundaries ).
185    */
186   void GetLineLayoutForBox( const LayoutParameters& parameters,
187                             LineLayout& lineLayout,
188                             CharacterDirection& paragraphDirection,
189                             bool completelyFill )
190   {
191     DALI_LOG_INFO( gLogFilter, Debug::Verbose, "-->GetLineLayoutForBox\n" );
192     DALI_LOG_INFO( gLogFilter, Debug::Verbose, "  initial glyph index : %d\n", lineLayout.glyphIndex );
193     // Stores temporary line layout which has not been added to the final line layout.
194     LineLayout tmpLineLayout;
195
196     const bool isMultiline = mLayout == MULTI_LINE_BOX;
197     const GlyphIndex lastGlyphIndex = parameters.totalNumberOfGlyphs - 1u;
198
199     // If the first glyph has a negative bearing its absolute value needs to be added to the line length.
200     // In the case the line starts with a right to left character, if the width is longer than the advance,
201     // the difference needs to be added to the line length.
202     const GlyphInfo& glyphInfo = *( parameters.glyphsBuffer + lineLayout.glyphIndex );
203
204     // Set the direction of the first character of the line.
205     lineLayout.characterIndex = *( parameters.glyphsToCharactersBuffer + lineLayout.glyphIndex );
206     const CharacterDirection firstCharacterDirection = ( NULL == parameters.characterDirectionBuffer ) ? false : *( parameters.characterDirectionBuffer + lineLayout.characterIndex );
207     CharacterDirection previousCharacterDirection = firstCharacterDirection;
208
209     const float extraWidth = glyphInfo.xBearing + glyphInfo.width - glyphInfo.advance;
210     float tmpExtraWidth = ( 0.f < extraWidth ) ? extraWidth : 0.f;
211
212     float tmpExtraBearing = ( 0.f > glyphInfo.xBearing ) ? -glyphInfo.xBearing : 0.f;
213
214     tmpLineLayout.length += mCursorWidth; // Added to give some space to the cursor.
215
216     // Calculate the line height if there is no characters.
217     FontId lastFontId = glyphInfo.fontId;
218     UpdateLineHeight( lastFontId, tmpLineLayout );
219
220     bool oneWordLaidOut = false;
221
222     const GlyphIndex lastGlyphPlusOne = parameters.startGlyphIndex + parameters.numberOfGlyphs;
223     for( GlyphIndex glyphIndex = lineLayout.glyphIndex;
224          glyphIndex < lastGlyphPlusOne;
225          ++glyphIndex )
226     {
227       DALI_LOG_INFO( gLogFilter, Debug::Verbose, "  glyph index : %d\n", glyphIndex );
228       const bool isLastGlyph = glyphIndex == lastGlyphIndex;
229
230       // Get the glyph info.
231       const GlyphInfo& glyphInfo = *( parameters.glyphsBuffer + glyphIndex );
232
233       // Check if the font of the current glyph is the same of the previous one.
234       // If it's different the ascender and descender need to be updated.
235       if( lastFontId != glyphInfo.fontId )
236       {
237         UpdateLineHeight( glyphInfo.fontId, tmpLineLayout );
238         lastFontId = glyphInfo.fontId;
239       }
240
241       // Get the character indices for the current glyph. The last character index is needed
242       // because there are glyphs formed by more than one character but their break info is
243       // given only for the last character.
244       const Length charactersPerGlyph = *( parameters.charactersPerGlyphBuffer + glyphIndex );
245       const CharacterIndex characterFirstIndex = *( parameters.glyphsToCharactersBuffer + glyphIndex );
246       const CharacterIndex characterLastIndex = characterFirstIndex + ( ( 1u > charactersPerGlyph ) ? 0u : charactersPerGlyph - 1u );
247
248       // Get the line break info for the current character.
249       const LineBreakInfo lineBreakInfo = *( parameters.lineBreakInfoBuffer + characterLastIndex );
250
251       // Get the word break info for the current character.
252       const WordBreakInfo wordBreakInfo = *( parameters.wordBreakInfoBuffer + characterLastIndex );
253
254       // Increase the number of characters.
255       tmpLineLayout.numberOfCharacters += charactersPerGlyph;
256
257       // Increase the number of glyphs.
258       tmpLineLayout.numberOfGlyphs++;
259
260       // Check whether is a white space.
261       const Character character = *( parameters.textBuffer + characterFirstIndex );
262       const bool isWhiteSpace = TextAbstraction::IsWhiteSpace( character );
263
264       // Used to restore the temporal line layout when a single word does not fit in the control's width and is split by character.
265       const float previousTmpLineLength = tmpLineLayout.length;
266       const float previousTmpExtraBearing = tmpExtraBearing;
267       const float previousTmpExtraWidth = tmpExtraWidth;
268
269       // Get the character's direction.
270       const CharacterDirection characterDirection = ( NULL == parameters.characterDirectionBuffer ) ? false : *( parameters.characterDirectionBuffer + characterFirstIndex );
271
272       // Increase the accumulated length.
273       if( isWhiteSpace )
274       {
275         // Add the length to the length of white spaces at the end of the line.
276         tmpLineLayout.wsLengthEndOfLine += glyphInfo.advance; // The advance is used as the width is always zero for the white spaces.
277       }
278       else
279       {
280         // Add as well any previous white space length.
281         tmpLineLayout.length += tmpLineLayout.wsLengthEndOfLine + glyphInfo.advance;
282
283         // An extra space may be added to the line for the first and last glyph of the line.
284         // If the bearing of the first glyph is negative, its positive value needs to be added.
285         // If the bearing plus the width of the last glyph is greater than the advance, the difference
286         // needs to be added.
287
288         if( characterDirection == paragraphDirection )
289         {
290           if( RTL == characterDirection )
291           {
292             //       <--
293             // |   Rrrrr|
294             // or
295             // |  Rllrrr|
296             // or
297             // |lllrrrrr|
298             // |     Rll|
299             //
300
301             tmpExtraBearing = ( 0.f > glyphInfo.xBearing ) ? -glyphInfo.xBearing : 0.f;
302           }
303           else // LTR
304           {
305             //  -->
306             // |lllL    |
307             // or
308             // |llrrL   |
309             // or
310             // |lllllrrr|
311             // |rrL     |
312             //
313
314             const float extraWidth = glyphInfo.xBearing + glyphInfo.width - glyphInfo.advance;
315             tmpExtraWidth = ( 0.f < extraWidth ) ? extraWidth : 0.f;
316           }
317         }
318         else
319         {
320           if( characterDirection != previousCharacterDirection )
321           {
322             if( RTL == characterDirection )
323             {
324               //  -->
325               // |lllR    |
326
327               const float extraWidth = glyphInfo.xBearing + glyphInfo.width - glyphInfo.advance;
328               tmpExtraWidth = ( 0.f < extraWidth ) ? extraWidth : 0.f;
329             }
330             else // LTR
331             {
332               //       <--
333               // |   Lrrrr|
334
335               tmpExtraBearing = ( 0.f > glyphInfo.xBearing ) ? -glyphInfo.xBearing : 0.f;
336             }
337           }
338           else if( characterDirection == firstCharacterDirection )
339           {
340             if( RTL == characterDirection )
341             {
342               //  -->
343               // |llllllrr|
344               // |Rr      |
345
346               tmpExtraBearing = ( 0.f > glyphInfo.xBearing ) ? -glyphInfo.xBearing : 0.f;
347             }
348             else // LTR
349             {
350               //       <--
351               // |llllrrrr|
352               // |     llL|
353
354               const float extraWidth = glyphInfo.xBearing + glyphInfo.width - glyphInfo.advance;
355               tmpExtraWidth = ( 0.f < extraWidth ) ? extraWidth : 0.f;
356             }
357           }
358         }
359
360         // Clear the white space length at the end of the line.
361         tmpLineLayout.wsLengthEndOfLine = 0.f;
362       }
363
364       // Check if the accumulated length fits in the width of the box.
365       if( ( completelyFill || isMultiline ) && !isWhiteSpace &&
366           ( tmpExtraBearing + lineLayout.length + lineLayout.wsLengthEndOfLine + tmpLineLayout.length + tmpExtraWidth > parameters.boundingBox.width ) )
367       {
368         // Current word does not fit in the box's width.
369         if( !oneWordLaidOut || completelyFill )
370         {
371           DALI_LOG_INFO( gLogFilter, Debug::Verbose, "  Break the word by character\n" );
372
373           // The word doesn't fit in the control's width. It needs to be split by character.
374           if( tmpLineLayout.numberOfGlyphs > 0u )
375           {
376             tmpLineLayout.numberOfCharacters -= charactersPerGlyph;
377             --tmpLineLayout.numberOfGlyphs;
378             tmpLineLayout.length = previousTmpLineLength;
379             tmpExtraBearing = previousTmpExtraBearing;
380             tmpExtraWidth = previousTmpExtraWidth;
381           }
382
383           // Add part of the word to the line layout.
384           MergeLineLayout( lineLayout, tmpLineLayout );
385         }
386         else
387         {
388           DALI_LOG_INFO( gLogFilter, Debug::Verbose, "  Current word does not fit.\n" );
389         }
390
391         lineLayout.extraBearing = tmpExtraBearing;
392         lineLayout.extraWidth = tmpExtraWidth;
393
394         DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--GetLineLayoutForBox.\n" );
395
396         return;
397       }
398
399       if( ( isMultiline || isLastGlyph ) &&
400           ( TextAbstraction::LINE_MUST_BREAK == lineBreakInfo ) )
401       {
402         // Must break the line. Update the line layout and return.
403         MergeLineLayout( lineLayout, tmpLineLayout );
404
405         // Set the next paragraph's direction.
406         if( !isLastGlyph &&
407             ( NULL != parameters.characterDirectionBuffer ) )
408         {
409           paragraphDirection = *( parameters.characterDirectionBuffer + 1u + characterLastIndex );
410         }
411
412         lineLayout.extraBearing = tmpExtraBearing;
413         lineLayout.extraWidth = tmpExtraWidth;
414
415         DALI_LOG_INFO( gLogFilter, Debug::Verbose, "  Must break\n" );
416         DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--GetLineLayoutForBox\n" );
417         return;
418       }
419
420       if( isMultiline &&
421           ( TextAbstraction::WORD_BREAK == wordBreakInfo ) )
422       {
423         oneWordLaidOut = true;
424         DALI_LOG_INFO( gLogFilter, Debug::Verbose, "  One word laid-out\n" );
425
426         // Current glyph is the last one of the current word.
427         // Add the temporal layout to the current one.
428         MergeLineLayout( lineLayout, tmpLineLayout );
429
430         tmpLineLayout.Clear();
431       }
432
433       previousCharacterDirection = characterDirection;
434     }
435
436     lineLayout.extraBearing = tmpExtraBearing;
437     lineLayout.extraWidth = tmpExtraWidth;
438
439     DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--GetLineLayoutForBox\n" );
440   }
441
442   /**
443    * @brief Calculates the vertical offset to add to the new laid-out glyphs.
444    *
445    * @pre @p lineIndex must be between 0 and the number of lines (both inclusive).
446    *
447    * @param[in] lines The previously laid-out lines.
448    * @param[in] lineIndex Index to the line where the new laid-out lines are inserted.
449    *
450    * @return The vertical offset of the lines starting from the beginning to the line @p lineIndex.
451    */
452   float SetParagraphOffset( const Vector<LineRun>& lines,
453                             LineIndex lineIndex )
454   {
455     float offset = 0.f;
456
457     for( Vector<LineRun>::ConstIterator it = lines.Begin(),
458            endIt = lines.Begin() + lineIndex;
459          it != endIt;
460          ++it )
461     {
462       const LineRun& line = *it;
463
464       offset += line.ascender + -line.descender;
465     }
466
467     return offset;
468   }
469
470   void SetGlyphPositions( const GlyphInfo* const glyphsBuffer,
471                           Length numberOfGlyphs,
472                           Vector2* glyphPositionsBuffer )
473   {
474     // Traverse the glyphs and set the positions.
475
476     // Check if the x bearing of the first character is negative.
477     // If it has a negative x bearing, it will exceed the boundaries of the actor,
478     // so the penX position needs to be moved to the right.
479
480     const GlyphInfo& glyph = *glyphsBuffer;
481     float penX = ( 0.f > glyph.xBearing ) ? -glyph.xBearing : 0.f;
482
483     for( GlyphIndex i = 0u; i < numberOfGlyphs; ++i )
484     {
485       const GlyphInfo& glyph = *( glyphsBuffer + i );
486       Vector2& position = *( glyphPositionsBuffer + i );
487
488       position.x = penX + glyph.xBearing;
489       position.y = -glyph.yBearing;
490
491       penX += glyph.advance;
492     }
493   }
494
495   /**
496    * @brief Resizes the line buffer.
497    *
498    * @param[in,out] lines The vector of lines. Used when the layout is created from scratch.
499    * @param[in,out] newLines The vector of lines used instead of @p lines when the layout is updated.
500    * @param[in,out] linesCapacity The capacity of the vector (either lines or newLines).
501    * @param[in] updateCurrentBuffer Whether the layout is updated.
502    *
503    * @return Pointer to either lines or newLines.
504    */
505   LineRun* ResizeLinesBuffer( Vector<LineRun>& lines,
506                               Vector<LineRun>& newLines,
507                               Length& linesCapacity,
508                               bool updateCurrentBuffer )
509   {
510     LineRun* linesBuffer = NULL;
511     // Reserve more space for the next lines.
512     linesCapacity *= 2u;
513     if( updateCurrentBuffer )
514     {
515       newLines.Resize( linesCapacity );
516       linesBuffer = newLines.Begin();
517     }
518     else
519     {
520       lines.Resize( linesCapacity );
521       linesBuffer = lines.Begin();
522     }
523
524     return linesBuffer;
525   }
526
527   /**
528    * Ellipsis a line if it exceeds the width's of the bounding box.
529    *
530    * @param[in] layoutParameters The parameters needed to layout the text.
531    * @param[in] layout The line layout.
532    * @param[in,out] layoutSize The text's layout size.
533    * @param[in,out] linesBuffer Pointer to the line's buffer.
534    * @param[in,out] glyphPositionsBuffer Pointer to the position's buffer.
535    * @param[in,out] numberOfLines The number of laid-out lines.
536    * @param[in] penY The vertical layout position.
537    * @param[in] currentParagraphDirection The current paragraph's direction.
538    *
539    * return Whether the line is ellipsized.
540    */
541   bool EllipsisLine( const LayoutParameters& layoutParameters,
542                      const LineLayout& layout,
543                      Size& layoutSize,
544                      LineRun* linesBuffer,
545                      Vector2* glyphPositionsBuffer,
546                      Length& numberOfLines,
547                      float penY,
548                      CharacterDirection currentParagraphDirection )
549   {
550     const bool ellipsis = ( ( penY - layout.descender > layoutParameters.boundingBox.height ) ||
551                             ( ( mLayout == SINGLE_LINE_BOX ) &&
552                               ( layout.extraBearing + layout.length + layout.extraWidth > layoutParameters.boundingBox.width ) ) );
553
554     if( ellipsis )
555     {
556       // Do not layout more lines if ellipsis is enabled.
557
558       // The last line needs to be completely filled with characters.
559       // Part of a word may be used.
560
561       LineRun* lineRun = NULL;
562       LineLayout ellipsisLayout;
563       if( 0u != numberOfLines )
564       {
565         // Get the last line and layout it again with the 'completelyFill' flag to true.
566         lineRun = linesBuffer + ( numberOfLines - 1u );
567
568         penY -= layout.ascender - lineRun->descender;
569
570         ellipsisLayout.glyphIndex = lineRun->glyphRun.glyphIndex;
571       }
572       else
573       {
574         // At least there is space reserved for one line.
575         lineRun = linesBuffer;
576
577         lineRun->glyphRun.glyphIndex = 0u;
578         ellipsisLayout.glyphIndex = 0u;
579
580         ++numberOfLines;
581       }
582
583       GetLineLayoutForBox( layoutParameters,
584                            ellipsisLayout,
585                            currentParagraphDirection,
586                            true );
587
588       lineRun->glyphRun.numberOfGlyphs = ellipsisLayout.numberOfGlyphs;
589       lineRun->characterRun.characterIndex = ellipsisLayout.characterIndex;
590       lineRun->characterRun.numberOfCharacters = ellipsisLayout.numberOfCharacters;
591       lineRun->width = ellipsisLayout.length;
592       lineRun->extraLength =  ( ellipsisLayout.wsLengthEndOfLine > 0.f ) ? ellipsisLayout.wsLengthEndOfLine - ellipsisLayout.extraWidth : 0.f;
593       lineRun->ascender = ellipsisLayout.ascender;
594       lineRun->descender = ellipsisLayout.descender;
595       lineRun->direction = !RTL;
596       lineRun->ellipsis = true;
597
598       layoutSize.width = layoutParameters.boundingBox.width;
599       layoutSize.height += ( lineRun->ascender + -lineRun->descender );
600
601       SetGlyphPositions( layoutParameters.glyphsBuffer + lineRun->glyphRun.glyphIndex,
602                          ellipsisLayout.numberOfGlyphs,
603                          glyphPositionsBuffer + lineRun->glyphRun.glyphIndex - layoutParameters.startGlyphIndex );
604     }
605
606     return ellipsis;
607   }
608
609   /**
610    * @brief Updates the text layout with a new laid-out line.
611    *
612    * @param[in] layoutParameters The parameters needed to layout the text.
613    * @param[in] layout The line layout.
614    * @param[in,out] layoutSize The text's layout size.
615    * @param[in,out] linesBuffer Pointer to the line's buffer.
616    * @param[in] index Index to the vector of glyphs.
617    * @param[in,out] numberOfLines The number of laid-out lines.
618    * @param[in] isLastLine Whether the laid-out line is the last one.
619    */
620   void UpdateTextLayout( const LayoutParameters& layoutParameters,
621                          const LineLayout& layout,
622                          Size& layoutSize,
623                          LineRun* linesBuffer,
624                          GlyphIndex index,
625                          Length& numberOfLines,
626                          bool isLastLine )
627   {
628     LineRun& lineRun = *( linesBuffer + numberOfLines );
629     ++numberOfLines;
630
631     lineRun.glyphRun.glyphIndex = index;
632     lineRun.glyphRun.numberOfGlyphs = layout.numberOfGlyphs;
633     lineRun.characterRun.characterIndex = layout.characterIndex;
634     lineRun.characterRun.numberOfCharacters = layout.numberOfCharacters;
635     if( isLastLine && !layoutParameters.isLastNewParagraph )
636     {
637       const float width = layout.extraBearing + layout.length + layout.extraWidth + layout.wsLengthEndOfLine;
638       if( MULTI_LINE_BOX == mLayout )
639       {
640         lineRun.width = ( width > layoutParameters.boundingBox.width ) ? layoutParameters.boundingBox.width : width;
641       }
642       else
643       {
644         lineRun.width = width;
645       }
646
647       lineRun.extraLength = 0.f;
648     }
649     else
650     {
651       lineRun.width = layout.extraBearing + layout.length + layout.extraWidth;
652       lineRun.extraLength = ( layout.wsLengthEndOfLine > 0.f ) ? layout.wsLengthEndOfLine - layout.extraWidth : 0.f;
653     }
654     lineRun.ascender = layout.ascender;
655     lineRun.descender = layout.descender;
656     lineRun.direction = !RTL;
657     lineRun.ellipsis = false;
658
659     // Update the actual size.
660     if( lineRun.width > layoutSize.width )
661     {
662       layoutSize.width = lineRun.width;
663     }
664
665     layoutSize.height += ( lineRun.ascender + -lineRun.descender );
666   }
667
668   /**
669    * @brief Updates the text layout with the last laid-out line.
670    *
671    * @param[in] layoutParameters The parameters needed to layout the text.
672    * @param[in] characterIndex The character index of the line.
673    * @param[in] glyphIndex The glyph index of the line.
674    * @param[in,out] layoutSize The text's layout size.
675    * @param[in,out] linesBuffer Pointer to the line's buffer.
676    * @param[in,out] numberOfLines The number of laid-out lines.
677    */
678   void UpdateTextLayout( const LayoutParameters& layoutParameters,
679                          CharacterIndex characterIndex,
680                          GlyphIndex glyphIndex,
681                          Size& layoutSize,
682                          LineRun* linesBuffer,
683                          Length& numberOfLines )
684   {
685     // Need to add a new line with no characters but with height to increase the layoutSize.height
686     const GlyphInfo& glyphInfo = *( layoutParameters.glyphsBuffer + layoutParameters.totalNumberOfGlyphs - 1u );
687
688     Text::FontMetrics fontMetrics;
689     mMetrics->GetFontMetrics( glyphInfo.fontId, fontMetrics );
690
691     LineRun& lineRun = *( linesBuffer + numberOfLines );
692     ++numberOfLines;
693
694     lineRun.glyphRun.glyphIndex = glyphIndex;
695     lineRun.glyphRun.numberOfGlyphs = 0u;
696     lineRun.characterRun.characterIndex = characterIndex;
697     lineRun.characterRun.numberOfCharacters = 0u;
698     lineRun.width = 0.f;
699     lineRun.ascender = fontMetrics.ascender;
700     lineRun.descender = fontMetrics.descender;
701     lineRun.extraLength = 0.f;
702     lineRun.alignmentOffset = 0.f;
703     lineRun.direction = !RTL;
704     lineRun.ellipsis = false;
705
706     layoutSize.height += ( lineRun.ascender + -lineRun.descender );
707   }
708
709   /**
710    * @brief Updates the text's layout size adding the size of the previously laid-out lines.
711    *
712    * @param[in] lines The vector of lines (before the new laid-out lines are inserted).
713    * @param[in,out] layoutSize The text's layout size.
714    */
715   void UpdateLayoutSize( const Vector<LineRun>& lines,
716                          Size& layoutSize )
717   {
718     for( Vector<LineRun>::ConstIterator it = lines.Begin(),
719            endIt = lines.End();
720          it != endIt;
721          ++it )
722     {
723       const LineRun& line = *it;
724
725       if( line.width > layoutSize.width )
726       {
727         layoutSize.width = line.width;
728       }
729
730       layoutSize.height += ( line.ascender + -line.descender );
731     }
732   }
733
734   /**
735    * @brief Updates the indices of the character and glyph runs of the lines before the new lines are inserted.
736    *
737    * @param[in] layoutParameters The parameters needed to layout the text.
738    * @param[in,out] lines The vector of lines (before the new laid-out lines are inserted).
739    * @param[in] characterOffset The offset to be added to the runs of characters.
740    * @param[in] glyphOffset The offset to be added to the runs of glyphs.
741    */
742   void UpdateLineIndexOffsets( const LayoutParameters& layoutParameters,
743                                Vector<LineRun>& lines,
744                                Length characterOffset,
745                                Length glyphOffset )
746   {
747     // Update the glyph and character runs.
748     for( Vector<LineRun>::Iterator it = lines.Begin() + layoutParameters.startLineIndex,
749            endIt = lines.End();
750          it != endIt;
751          ++it )
752     {
753       LineRun& line = *it;
754
755       line.glyphRun.glyphIndex = glyphOffset;
756       line.characterRun.characterIndex = characterOffset;
757
758       glyphOffset += line.glyphRun.numberOfGlyphs;
759       characterOffset += line.characterRun.numberOfCharacters;
760     }
761   }
762
763   bool LayoutText( const LayoutParameters& layoutParameters,
764                    Vector<Vector2>& glyphPositions,
765                    Vector<LineRun>& lines,
766                    Size& layoutSize )
767   {
768     DALI_LOG_INFO( gLogFilter, Debug::Verbose, "-->LayoutText\n" );
769     DALI_LOG_INFO( gLogFilter, Debug::Verbose, "  box size %f, %f\n", layoutParameters.boundingBox.width, layoutParameters.boundingBox.height );
770
771     if( 0u == layoutParameters.numberOfGlyphs )
772     {
773       // Add an extra line if the last character is a new paragraph character and the last line doesn't have zero characters.
774       if( layoutParameters.isLastNewParagraph )
775       {
776         Length numberOfLines = lines.Count();
777         if( 0u != numberOfLines )
778         {
779           const LineRun& lastLine = *( lines.End() - 1u );
780
781           if( 0u != lastLine.characterRun.numberOfCharacters )
782           {
783             // Need to add a new line with no characters but with height to increase the layoutSize.height
784             LineRun newLine;
785             lines.PushBack( newLine );
786
787             UpdateTextLayout( layoutParameters,
788                               lastLine.characterRun.characterIndex + lastLine.characterRun.numberOfCharacters,
789                               lastLine.glyphRun.glyphIndex + lastLine.glyphRun.numberOfGlyphs,
790                               layoutSize,
791                               lines.Begin(),
792                               numberOfLines );
793           }
794         }
795       }
796
797       // Nothing else do if there are no glyphs to layout.
798       return false;
799     }
800
801     const GlyphIndex lastGlyphPlusOne = layoutParameters.startGlyphIndex + layoutParameters.numberOfGlyphs;
802
803     // In a previous layout, an extra line with no characters may have been added if the text ended with a new paragraph character.
804     // This extra line needs to be removed.
805     if( 0u != lines.Count() )
806     {
807       Vector<LineRun>::Iterator lastLine = lines.End() - 1u;
808
809       if( ( 0u == lastLine->characterRun.numberOfCharacters ) &&
810           ( lastGlyphPlusOne == layoutParameters.totalNumberOfGlyphs ) )
811       {
812         lines.Remove( lastLine );
813       }
814     }
815
816     // Set the first paragraph's direction.
817     CharacterDirection paragraphDirection = ( NULL != layoutParameters.characterDirectionBuffer ) ? *layoutParameters.characterDirectionBuffer : !RTL;
818
819     // Whether the layout is being updated or set from scratch.
820     const bool updateCurrentBuffer = layoutParameters.numberOfGlyphs < layoutParameters.totalNumberOfGlyphs;
821
822     Vector2* glyphPositionsBuffer = NULL;
823     Vector<Vector2> newGlyphPositions;
824
825     LineRun* linesBuffer = NULL;
826     Vector<LineRun> newLines;
827
828     // Estimate the number of lines.
829     Length linesCapacity = layoutParameters.estimatedNumberOfLines;
830     Length numberOfLines = 0u;
831
832     if( updateCurrentBuffer )
833     {
834       newGlyphPositions.Resize( layoutParameters.numberOfGlyphs );
835       glyphPositionsBuffer = newGlyphPositions.Begin();
836
837       newLines.Resize( linesCapacity );
838       linesBuffer = newLines.Begin();
839     }
840     else
841     {
842       glyphPositionsBuffer = glyphPositions.Begin();
843
844       lines.Resize( linesCapacity );
845       linesBuffer = lines.Begin();
846     }
847
848     float penY = SetParagraphOffset( lines,
849                                      layoutParameters.startLineIndex );
850
851     for( GlyphIndex index = layoutParameters.startGlyphIndex; index < lastGlyphPlusOne; )
852     {
853       CharacterDirection currentParagraphDirection = paragraphDirection;
854
855       // Get the layout for the line.
856       LineLayout layout;
857       layout.glyphIndex = index;
858       GetLineLayoutForBox( layoutParameters,
859                            layout,
860                            paragraphDirection,
861                            false );
862
863       DALI_LOG_INFO( gLogFilter, Debug::Verbose, "           glyph index %d\n", layout.glyphIndex );
864       DALI_LOG_INFO( gLogFilter, Debug::Verbose, "       character index %d\n", layout.characterIndex );
865       DALI_LOG_INFO( gLogFilter, Debug::Verbose, "      number of glyphs %d\n", layout.numberOfGlyphs );
866       DALI_LOG_INFO( gLogFilter, Debug::Verbose, "  number of characters %d\n", layout.numberOfCharacters );
867       DALI_LOG_INFO( gLogFilter, Debug::Verbose, "                length %f\n", layout.length );
868
869       if( 0u == layout.numberOfGlyphs )
870       {
871         // The width is too small and no characters are laid-out.
872         DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--LayoutText width too small!\n\n" );
873
874         lines.Resize( numberOfLines );
875         return false;
876       }
877
878       // Set the line position. Discard if ellipsis is enabled and the position exceeds the boundaries
879       // of the box.
880       penY += layout.ascender;
881
882       DALI_LOG_INFO( gLogFilter, Debug::Verbose, "  pen y %f\n", penY );
883
884       bool ellipsis = false;
885       if( mEllipsisEnabled )
886       {
887         // Does the ellipsis of the last line.
888         ellipsis = EllipsisLine( layoutParameters,
889                                  layout,
890                                  layoutSize,
891                                  linesBuffer,
892                                  glyphPositionsBuffer,
893                                  numberOfLines,
894                                  penY,
895                                  currentParagraphDirection );
896       }
897
898       if( ellipsis )
899       {
900         // No more lines to layout.
901         break;
902       }
903       else
904       {
905         // Whether the last line has been laid-out.
906         const bool isLastLine = index + layout.numberOfGlyphs == layoutParameters.totalNumberOfGlyphs;
907
908         if( numberOfLines == linesCapacity )
909         {
910           // Reserve more space for the next lines.
911           linesBuffer = ResizeLinesBuffer( lines,
912                                            newLines,
913                                            linesCapacity,
914                                            updateCurrentBuffer );
915         }
916
917         // Updates the current text's layout with the line's layout.
918         UpdateTextLayout( layoutParameters,
919                           layout,
920                           layoutSize,
921                           linesBuffer,
922                           index,
923                           numberOfLines,
924                           isLastLine );
925
926         const GlyphIndex nextIndex = index + layout.numberOfGlyphs;
927
928         if( ( nextIndex == layoutParameters.totalNumberOfGlyphs ) &&
929             layoutParameters.isLastNewParagraph &&
930             ( mLayout == MULTI_LINE_BOX ) )
931         {
932           // The last character of the text is a new paragraph character.
933           // An extra line with no characters is added to increase the text's height
934           // in order to place the cursor.
935
936           if( numberOfLines == linesCapacity )
937           {
938             // Reserve more space for the next lines.
939             linesBuffer = ResizeLinesBuffer( lines,
940                                              newLines,
941                                              linesCapacity,
942                                              updateCurrentBuffer );
943           }
944
945           UpdateTextLayout( layoutParameters,
946                             layout.characterIndex + layout.numberOfCharacters,
947                             index + layout.numberOfGlyphs,
948                             layoutSize,
949                             linesBuffer,
950                             numberOfLines );
951         } // whether to add a last line.
952
953         // Sets the positions of the glyphs.
954         SetGlyphPositions( layoutParameters.glyphsBuffer + index,
955                            layout.numberOfGlyphs,
956                            glyphPositionsBuffer + index - layoutParameters.startGlyphIndex );
957
958         // Updates the vertical pen's position.
959         penY += -layout.descender;
960
961         // Increase the glyph index.
962         index = nextIndex;
963       } // no ellipsis
964     } // end for() traversing glyphs.
965
966     if( updateCurrentBuffer )
967     {
968       glyphPositions.Insert( glyphPositions.Begin() + layoutParameters.startGlyphIndex,
969                              newGlyphPositions.Begin(),
970                              newGlyphPositions.End() );
971       glyphPositions.Resize( layoutParameters.totalNumberOfGlyphs );
972
973       newLines.Resize( numberOfLines );
974
975       // Current text's layout size adds only the newly laid-out lines.
976       // Updates the layout size with the previously laid-out lines.
977       UpdateLayoutSize( lines,
978                         layoutSize );
979
980       if( 0u != newLines.Count() )
981       {
982         const LineRun& lastLine = *( newLines.End() - 1u );
983
984         const Length characterOffset = lastLine.characterRun.characterIndex + lastLine.characterRun.numberOfCharacters;
985         const Length glyphOffset = lastLine.glyphRun.glyphIndex + lastLine.glyphRun.numberOfGlyphs;
986
987         // Update the indices of the runs before the new laid-out lines are inserted.
988         UpdateLineIndexOffsets( layoutParameters,
989                                 lines,
990                                 characterOffset,
991                                 glyphOffset );
992
993         // Insert the lines.
994         lines.Insert( lines.Begin() + layoutParameters.startLineIndex,
995                       newLines.Begin(),
996                       newLines.End() );
997       }
998     }
999     else
1000     {
1001       lines.Resize( numberOfLines );
1002     }
1003
1004     DALI_LOG_INFO( gLogFilter, Debug::Verbose, "<--LayoutText\n\n" );
1005
1006     return true;
1007   }
1008
1009   void ReLayoutRightToLeftLines( const LayoutParameters& layoutParameters,
1010                                  CharacterIndex startIndex,
1011                                  Length numberOfCharacters,
1012                                  Vector<Vector2>& glyphPositions )
1013   {
1014     const CharacterIndex lastCharacterIndex = startIndex + numberOfCharacters;
1015
1016     // Traverses the paragraphs with right to left characters.
1017     for( LineIndex lineIndex = 0u; lineIndex < layoutParameters.numberOfBidirectionalInfoRuns; ++lineIndex )
1018     {
1019       const BidirectionalLineInfoRun& bidiLine = *( layoutParameters.lineBidirectionalInfoRunsBuffer + lineIndex );
1020
1021       if( startIndex >= bidiLine.characterRun.characterIndex + bidiLine.characterRun.numberOfCharacters )
1022       {
1023         // Do not reorder the line if it has been already reordered.
1024         continue;
1025       }
1026
1027       if( bidiLine.characterRun.characterIndex >= lastCharacterIndex )
1028       {
1029         // Do not reorder the lines after the last requested character.
1030         break;
1031       }
1032
1033       const CharacterIndex characterVisualIndex = bidiLine.characterRun.characterIndex + *bidiLine.visualToLogicalMap;
1034       const GlyphInfo& glyph = *( layoutParameters.glyphsBuffer + *( layoutParameters.charactersToGlyphsBuffer + characterVisualIndex ) );
1035
1036       float penX = ( 0.f > glyph.xBearing ) ? -glyph.xBearing : 0.f;
1037
1038       Vector2* glyphPositionsBuffer = glyphPositions.Begin();
1039
1040       // Traverses the characters of the right to left paragraph.
1041       for( CharacterIndex characterLogicalIndex = 0u;
1042            characterLogicalIndex < bidiLine.characterRun.numberOfCharacters;
1043            ++characterLogicalIndex )
1044       {
1045         // Convert the character in the logical order into the character in the visual order.
1046         const CharacterIndex characterVisualIndex = bidiLine.characterRun.characterIndex + *( bidiLine.visualToLogicalMap + characterLogicalIndex );
1047
1048         // Get the number of glyphs of the character.
1049         const Length numberOfGlyphs = *( layoutParameters.glyphsPerCharacterBuffer + characterVisualIndex );
1050
1051         for( GlyphIndex index = 0u; index < numberOfGlyphs; ++index )
1052         {
1053           // Convert the character in the visual order into the glyph in the visual order.
1054           const GlyphIndex glyphIndex = *( layoutParameters.charactersToGlyphsBuffer + characterVisualIndex ) + index;
1055
1056           DALI_ASSERT_DEBUG( 0u <= glyphIndex && glyphIndex < layoutParameters.totalNumberOfGlyphs );
1057
1058           const GlyphInfo& glyph = *( layoutParameters.glyphsBuffer + glyphIndex );
1059           Vector2& position = *( glyphPositionsBuffer + glyphIndex );
1060
1061           position.x = penX + glyph.xBearing;
1062           penX += glyph.advance;
1063         }
1064       }
1065     }
1066   }
1067
1068   void Align( const Size& size,
1069               CharacterIndex startIndex,
1070               Length numberOfCharacters,
1071               Vector<LineRun>& lines )
1072   {
1073     const CharacterIndex lastCharacterPlusOne = startIndex + numberOfCharacters;
1074
1075     // Traverse all lines and align the glyphs.
1076     for( Vector<LineRun>::Iterator it = lines.Begin(), endIt = lines.End();
1077          it != endIt;
1078          ++it )
1079     {
1080       LineRun& line = *it;
1081
1082       if( line.characterRun.characterIndex < startIndex )
1083       {
1084         // Do not align lines which have already been aligned.
1085         continue;
1086       }
1087
1088       if( line.characterRun.characterIndex >= lastCharacterPlusOne )
1089       {
1090         // Do not align lines beyond the last laid-out character.
1091         break;
1092       }
1093
1094       // Calculate the line's alignment offset accordingly with the align option,
1095       // the box width, line length, and the paragraph's direction.
1096       CalculateHorizontalAlignment( size.width,
1097                                     line );
1098     }
1099   }
1100
1101   void CalculateHorizontalAlignment( float boxWidth,
1102                                      LineRun& line )
1103   {
1104     line.alignmentOffset = 0.f;
1105     const bool isRTL = RTL == line.direction;
1106     float lineLength = line.width;
1107
1108     HorizontalAlignment alignment = mHorizontalAlignment;
1109     if( isRTL )
1110     {
1111       // Swap the alignment type if the line is right to left.
1112       switch( alignment )
1113       {
1114         case HORIZONTAL_ALIGN_BEGIN:
1115         {
1116           alignment = HORIZONTAL_ALIGN_END;
1117           break;
1118         }
1119         case HORIZONTAL_ALIGN_CENTER:
1120         {
1121           // Nothing to do.
1122           break;
1123         }
1124         case HORIZONTAL_ALIGN_END:
1125         {
1126           alignment = HORIZONTAL_ALIGN_BEGIN;
1127           break;
1128         }
1129       }
1130     }
1131
1132     // Calculate the horizontal line offset.
1133     switch( alignment )
1134     {
1135       case HORIZONTAL_ALIGN_BEGIN:
1136       {
1137         line.alignmentOffset = 0.f;
1138
1139         if( isRTL )
1140         {
1141           // 'Remove' the white spaces at the end of the line (which are at the beginning in visual order)
1142           line.alignmentOffset -= line.extraLength;
1143         }
1144         break;
1145       }
1146       case HORIZONTAL_ALIGN_CENTER:
1147       {
1148         line.alignmentOffset = 0.5f * ( boxWidth - lineLength );
1149
1150         if( isRTL )
1151         {
1152           line.alignmentOffset -= line.extraLength;
1153         }
1154
1155         line.alignmentOffset = floorf( line.alignmentOffset ); // try to avoid pixel alignment.
1156         break;
1157       }
1158       case HORIZONTAL_ALIGN_END:
1159       {
1160         if( isRTL )
1161         {
1162           lineLength += line.extraLength;
1163         }
1164
1165         line.alignmentOffset = boxWidth - lineLength;
1166         break;
1167       }
1168     }
1169   }
1170
1171   LayoutEngine::Layout mLayout;
1172   LayoutEngine::HorizontalAlignment mHorizontalAlignment;
1173   LayoutEngine::VerticalAlignment mVerticalAlignment;
1174   float mCursorWidth;
1175
1176   IntrusivePtr<Metrics> mMetrics;
1177
1178   bool mEllipsisEnabled:1;
1179 };
1180
1181 LayoutEngine::LayoutEngine()
1182 : mImpl( NULL )
1183 {
1184   mImpl = new LayoutEngine::Impl();
1185 }
1186
1187 LayoutEngine::~LayoutEngine()
1188 {
1189   delete mImpl;
1190 }
1191
1192 void LayoutEngine::SetMetrics( MetricsPtr& metrics )
1193 {
1194   mImpl->mMetrics = metrics;
1195 }
1196
1197 void LayoutEngine::SetLayout( Layout layout )
1198 {
1199   mImpl->mLayout = layout;
1200 }
1201
1202 LayoutEngine::Layout LayoutEngine::GetLayout() const
1203 {
1204   return mImpl->mLayout;
1205 }
1206
1207 void LayoutEngine::SetTextEllipsisEnabled( bool enabled )
1208 {
1209   mImpl->mEllipsisEnabled = enabled;
1210 }
1211
1212 bool LayoutEngine::GetTextEllipsisEnabled() const
1213 {
1214   return mImpl->mEllipsisEnabled;
1215 }
1216
1217 void LayoutEngine::SetHorizontalAlignment( HorizontalAlignment alignment )
1218 {
1219   mImpl->mHorizontalAlignment = alignment;
1220 }
1221
1222 LayoutEngine::HorizontalAlignment LayoutEngine::GetHorizontalAlignment() const
1223 {
1224   return mImpl->mHorizontalAlignment;
1225 }
1226
1227 void LayoutEngine::SetVerticalAlignment( VerticalAlignment alignment )
1228 {
1229   mImpl->mVerticalAlignment = alignment;
1230 }
1231
1232 LayoutEngine::VerticalAlignment LayoutEngine::GetVerticalAlignment() const
1233 {
1234   return mImpl->mVerticalAlignment;
1235 }
1236
1237 void LayoutEngine::SetCursorWidth( int width )
1238 {
1239   mImpl->mCursorWidth = static_cast<float>( width );
1240 }
1241
1242 int LayoutEngine::GetCursorWidth() const
1243 {
1244   return static_cast<int>( mImpl->mCursorWidth );
1245 }
1246
1247 bool LayoutEngine::LayoutText( const LayoutParameters& layoutParameters,
1248                                Vector<Vector2>& glyphPositions,
1249                                Vector<LineRun>& lines,
1250                                Size& layoutSize )
1251 {
1252   return mImpl->LayoutText( layoutParameters,
1253                             glyphPositions,
1254                             lines,
1255                             layoutSize );
1256 }
1257
1258 void LayoutEngine::ReLayoutRightToLeftLines( const LayoutParameters& layoutParameters,
1259                                              CharacterIndex startIndex,
1260                                              Length numberOfCharacters,
1261                                              Vector<Vector2>& glyphPositions )
1262 {
1263   mImpl->ReLayoutRightToLeftLines( layoutParameters,
1264                                    startIndex,
1265                                    numberOfCharacters,
1266                                    glyphPositions );
1267 }
1268
1269 void LayoutEngine::Align( const Size& size,
1270                           CharacterIndex startIndex,
1271                           Length numberOfCharacters,
1272                           Vector<LineRun>& lines )
1273 {
1274   mImpl->Align( size,
1275                 startIndex,
1276                 numberOfCharacters,
1277                 lines );
1278 }
1279
1280 } // namespace Text
1281
1282 } // namespace Toolkit
1283
1284 } // namespace Dali