[dali_2.3.21] Merge branch 'devel/master'
[platform/core/uifw/dali-toolkit.git] / dali-toolkit / internal / text / controller / text-controller-relayouter.cpp
1 /*
2  * Copyright (c) 2022 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/controller/text-controller-relayouter.h>
20
21 // EXTERNAL INCLUDES
22 #include <cmath>
23 #include <dali/integration-api/debug.h>
24 #include <dali/integration-api/trace.h>
25 #include <dali/public-api/common/constants.h>
26 #include <dali/public-api/math/math-utils.h>
27 #include <limits>
28
29 // INTERNAL INCLUDES
30 #include <dali-toolkit/internal/text/controller/text-controller-event-handler.h>
31 #include <dali-toolkit/internal/text/controller/text-controller-impl.h>
32 #include <dali-toolkit/internal/text/layouts/layout-parameters.h>
33
34 namespace
35 {
36 #if defined(DEBUG_ENABLED)
37 Debug::Filter* gLogFilter = Debug::Filter::New(Debug::NoLogging, true, "LOG_TEXT_CONTROLS");
38 #endif
39
40 DALI_INIT_TRACE_FILTER(gTraceFilter, DALI_TRACE_TEXT_PERFORMANCE_MARKER, false);
41 DALI_INIT_TRACE_FILTER(gTraceFilter2, DALI_TRACE_PERFORMANCE_MARKER, false);
42
43 constexpr float MAX_FLOAT = std::numeric_limits<float>::max();
44
45 float ConvertToEven(float value)
46 {
47   int intValue(static_cast<int>(value));
48   return static_cast<float>(intValue + (intValue & 1));
49 }
50
51 } // namespace
52
53 namespace Dali
54 {
55 namespace Toolkit
56 {
57 namespace Text
58 {
59 Size Controller::Relayouter::CalculateLayoutSizeOnRequiredControllerSize(Controller& controller, const Size& requestedControllerSize, const OperationsMask& requestedOperationsMask)
60 {
61   DALI_LOG_INFO(gLogFilter, Debug::Verbose, "-->CalculateLayoutSizeOnRequiredControllerSize\n");
62   Size calculatedLayoutSize;
63
64   Controller::Impl& impl        = *controller.mImpl;
65   ModelPtr&         model       = impl.mModel;
66   VisualModelPtr&   visualModel = model->mVisualModel;
67
68   // Operations that can be done only once until the text changes.
69   const OperationsMask onlyOnceOperations = static_cast<OperationsMask>(CONVERT_TO_UTF32 |
70                                                                         GET_SCRIPTS |
71                                                                         VALIDATE_FONTS |
72                                                                         GET_LINE_BREAKS |
73                                                                         BIDI_INFO |
74                                                                         SHAPE_TEXT |
75                                                                         GET_GLYPH_METRICS);
76
77   const OperationsMask sizeOperations = static_cast<OperationsMask>(LAYOUT | ALIGN | REORDER);
78
79   // Set the update info to relayout the whole text.
80   TextUpdateInfo& textUpdateInfo = impl.mTextUpdateInfo;
81   if((0 == textUpdateInfo.mNumberOfCharactersToAdd) &&
82      (0 == textUpdateInfo.mPreviousNumberOfCharacters) &&
83      ((visualModel->mControlSize.width < Math::MACHINE_EPSILON_1000) || (visualModel->mControlSize.height < Math::MACHINE_EPSILON_1000)))
84   {
85     textUpdateInfo.mNumberOfCharactersToAdd = model->mLogicalModel->mText.Count();
86   }
87   textUpdateInfo.mParagraphCharacterIndex     = 0u;
88   textUpdateInfo.mRequestedNumberOfCharacters = model->mLogicalModel->mText.Count();
89
90   // Get a reference to the pending operations member
91   OperationsMask& operationsPending = impl.mOperationsPending;
92
93   // Store the actual control's size to restore later.
94   const Size actualControlSize = visualModel->mControlSize;
95
96   // This is to keep Index to the first character to be updated.
97   // Then restore it after calling Clear method.
98   auto updateInfoCharIndexBackup = textUpdateInfo.mCharacterIndex;
99
100   // Whether the text control is editable
101   const bool isEditable = NULL != impl.mEventData;
102
103   if(!isEditable)
104   {
105     if(NO_OPERATION != (VALIDATE_FONTS & operationsPending) &&
106        textUpdateInfo.mCharacterIndex == static_cast<CharacterIndex>(-1))
107     {
108       impl.ClearFontData();
109       updateInfoCharIndexBackup = textUpdateInfo.mCharacterIndex;
110     }
111
112     impl.UpdateModel(onlyOnceOperations);
113
114     // Layout the text for the new width.
115     operationsPending = static_cast<OperationsMask>(operationsPending | requestedOperationsMask);
116
117     DoRelayout(impl,
118                requestedControllerSize,
119                static_cast<OperationsMask>(onlyOnceOperations | requestedOperationsMask),
120                calculatedLayoutSize);
121
122     textUpdateInfo.Clear();
123     textUpdateInfo.mClearAll = true;
124
125     // Do not do again the only once operations.
126     operationsPending = static_cast<OperationsMask>(operationsPending & ~onlyOnceOperations);
127   }
128   else
129   {
130     // Layout the text for the new width.
131     // Apply the pending operations, requested operations and the only once operations.
132     // Then remove onlyOnceOperations
133     operationsPending = static_cast<OperationsMask>(operationsPending | requestedOperationsMask | onlyOnceOperations);
134
135     // Make sure the model is up-to-date before layouting
136     impl.UpdateModel(static_cast<OperationsMask>(operationsPending & ~UPDATE_LAYOUT_SIZE));
137
138     DoRelayout(impl,
139                requestedControllerSize,
140                static_cast<OperationsMask>(operationsPending & ~UPDATE_LAYOUT_SIZE),
141                calculatedLayoutSize);
142
143     // Clear the update info. This info will be set the next time the text is updated.
144     textUpdateInfo.Clear();
145
146     //TODO: Refactor "DoRelayout" and extract common code of size calculation without modifying attributes of mVisualModel,
147     //TODO: then calculate GlyphPositions. Lines, Size, Layout for Natural-Size
148     //TODO: and utilize the values in OperationsPending and TextUpdateInfo without changing the original one.
149     //TODO: Also it will improve performance because there is no need todo FullRelyout on the next need for layouting.
150   }
151
152   // FullRelayoutNeeded should be true because DoRelayout is MAX_FLOAT, MAX_FLOAT.
153   // By this no need to take backup and restore it.
154   textUpdateInfo.mFullRelayoutNeeded = true;
155
156   // Restore mCharacterIndex. Because "Clear" set it to the maximum integer.
157   // The "CalculateTextUpdateIndices" does not work proprely because the mCharacterIndex will be greater than mPreviousNumberOfCharacters.
158   // Which apply an assumption to update only the last  paragraph. That could cause many of out of index crashes.
159   textUpdateInfo.mCharacterIndex = updateInfoCharIndexBackup;
160
161   // Do the size related operations again.
162   operationsPending = static_cast<OperationsMask>(operationsPending | sizeOperations);
163
164   // Restore the actual control's size.
165   visualModel->mControlSize = actualControlSize;
166
167   return calculatedLayoutSize;
168 }
169
170 Vector3 Controller::Relayouter::GetNaturalSize(Controller& controller)
171 {
172   DALI_LOG_INFO(gLogFilter, Debug::Verbose, "-->Controller::GetNaturalSize\n");
173   DALI_TRACE_SCOPE(gTraceFilter, "DALI_TEXT_GET_NATURAL_SIZE");
174   Vector3 naturalSizeVec3;
175
176   // Make sure the model is up-to-date before layouting
177   EventHandler::ProcessModifyEvents(controller);
178
179   Controller::Impl& impl        = *controller.mImpl;
180   ModelPtr&         model       = impl.mModel;
181   VisualModelPtr&   visualModel = model->mVisualModel;
182
183   if(impl.mRecalculateNaturalSize)
184   {
185     Size naturalSize;
186
187     // Layout the text for the new width.
188     OperationsMask requestedOperationsMask  = static_cast<OperationsMask>(LAYOUT | REORDER);
189     Size           sizeMaxWidthAndMaxHeight = Size(MAX_FLOAT, MAX_FLOAT);
190
191     naturalSize = CalculateLayoutSizeOnRequiredControllerSize(controller, sizeMaxWidthAndMaxHeight, requestedOperationsMask);
192
193     // Stores the natural size to avoid recalculate it again
194     // unless the text/style changes.
195     visualModel->SetNaturalSize(naturalSize);
196     naturalSizeVec3 = naturalSize;
197
198     impl.mRecalculateNaturalSize = false;
199
200     DALI_LOG_INFO(gLogFilter, Debug::Verbose, "<--Controller::GetNaturalSize calculated %f,%f,%f\n", naturalSizeVec3.x, naturalSizeVec3.y, naturalSizeVec3.z);
201   }
202   else
203   {
204     naturalSizeVec3 = visualModel->GetNaturalSize();
205
206     DALI_LOG_INFO(gLogFilter, Debug::Verbose, "<--Controller::GetNaturalSize cached %f,%f,%f\n", naturalSizeVec3.x, naturalSizeVec3.y, naturalSizeVec3.z);
207   }
208
209   naturalSizeVec3.x = ConvertToEven(naturalSizeVec3.x);
210   naturalSizeVec3.y = ConvertToEven(naturalSizeVec3.y);
211
212   return naturalSizeVec3;
213 }
214
215 bool Controller::Relayouter::CheckForTextFit(Controller& controller, float pointSize, const Size& layoutSize)
216 {
217   Size              textSize;
218   Controller::Impl& impl            = *controller.mImpl;
219   TextUpdateInfo&   textUpdateInfo  = impl.mTextUpdateInfo;
220   impl.mFontDefaults->mFitPointSize = pointSize;
221   impl.mFontDefaults->sizeDefined   = true;
222   impl.ClearFontData();
223
224   // Operations that can be done only once until the text changes.
225   const OperationsMask onlyOnceOperations = static_cast<OperationsMask>(CONVERT_TO_UTF32 |
226                                                                         GET_SCRIPTS |
227                                                                         VALIDATE_FONTS |
228                                                                         GET_LINE_BREAKS |
229                                                                         BIDI_INFO |
230                                                                         SHAPE_TEXT |
231                                                                         GET_GLYPH_METRICS);
232
233   textUpdateInfo.mParagraphCharacterIndex     = 0u;
234   textUpdateInfo.mRequestedNumberOfCharacters = impl.mModel->mLogicalModel->mText.Count();
235
236   // Make sure the model is up-to-date before layouting
237   impl.UpdateModel(onlyOnceOperations);
238
239   bool layoutTooSmall = false;
240   DoRelayout(impl,
241              Size(layoutSize.width, MAX_FLOAT),
242              static_cast<OperationsMask>(onlyOnceOperations | LAYOUT),
243              textSize,
244              layoutTooSmall);
245
246   // Clear the update info. This info will be set the next time the text is updated.
247   textUpdateInfo.Clear();
248   textUpdateInfo.mClearAll = true;
249
250   if(layoutTooSmall || textSize.width > layoutSize.width || textSize.height > layoutSize.height)
251   {
252     return false;
253   }
254   return true;
255 }
256
257 void Controller::Relayouter::FitArrayPointSizeforLayout(Controller& controller, const Size& layoutSize)
258 {
259   Controller::Impl& impl = *controller.mImpl;
260
261   const OperationsMask operations = impl.mOperationsPending;
262   if(NO_OPERATION != (UPDATE_LAYOUT_SIZE & operations) || impl.mTextFitContentSize != layoutSize)
263   {
264     DALI_TRACE_SCOPE(gTraceFilter, "DALI_TEXT_FIT_ARRAY_LAYOUT");
265     std::vector<Toolkit::DevelTextLabel::FitOption> fitOptions = impl.mTextFitArray;
266     int numberOfFitOptions = static_cast<int>(fitOptions.size());
267     if(numberOfFitOptions == 0)
268     {
269       DALI_LOG_ERROR("fitOptions is empty\n");
270       return;
271     }
272
273     ModelPtr& model          = impl.mModel;
274     bool      actualellipsis = model->mElideEnabled;
275     model->mElideEnabled     = false;
276
277     // Sort in ascending order by PointSize.
278     std::sort(fitOptions.begin(), fitOptions.end(), compareByPointSize);
279
280     // Decide whether to use binary search.
281     // If MinLineSize is not sorted in ascending order,
282     // binary search cannot guarantee that it will always find the best value.
283     bool  binarySearch    = true;
284     float prevMinLineSize = 0.0f;
285     for(Toolkit::DevelTextLabel::FitOption& option : fitOptions)
286     {
287       float optionMinLineSize = option.GetMinLineSize();
288       if(prevMinLineSize > optionMinLineSize)
289       {
290         binarySearch = false;
291         break;
292       }
293       prevMinLineSize = optionMinLineSize;
294     }
295
296     // Set the first FitOption(Minimum PointSize) to the best value.
297     // If the search does not find an optimal value, the minimum PointSize will be used to text fit.
298     Toolkit::DevelTextLabel::FitOption firstOption = fitOptions.front();
299     bool  bestSizeUpdatedLatest = false;
300     float bestPointSize         = firstOption.GetPointSize();
301     float bestMinLineSize       = firstOption.GetMinLineSize();
302
303     if(binarySearch)
304     {
305       int left = 0u;
306       int right = numberOfFitOptions - 1;
307
308       while (left <= right)
309       {
310         int mid = left + (right - left) / 2;
311         Toolkit::DevelTextLabel::FitOption option = fitOptions[mid];
312         float testPointSize   = option.GetPointSize();
313         float testMinLineSize = option.GetMinLineSize();
314         impl.SetDefaultLineSize(testMinLineSize);
315
316         if(CheckForTextFit(controller, testPointSize, layoutSize))
317         {
318           bestSizeUpdatedLatest = true;
319           bestPointSize   = testPointSize;
320           bestMinLineSize = testMinLineSize;
321           left = mid + 1;
322         }
323         else
324         {
325           bestSizeUpdatedLatest = false;
326           right = mid - 1;
327         }
328       }
329     }
330     else
331     {
332       // If binary search is not possible, search sequentially starting from the largest PointSize.
333       for(auto it = fitOptions.rbegin(); it != fitOptions.rend(); ++it)
334       {
335         Toolkit::DevelTextLabel::FitOption option = *it;
336         float testPointSize   = option.GetPointSize();
337         float testMinLineSize = option.GetMinLineSize();
338         impl.SetDefaultLineSize(testMinLineSize);
339
340         if(CheckForTextFit(controller, testPointSize, layoutSize))
341         {
342           bestSizeUpdatedLatest = true;
343           bestPointSize   = testPointSize;
344           bestMinLineSize = testMinLineSize;
345           break;
346         }
347         else
348         {
349           bestSizeUpdatedLatest = false;
350         }
351       }
352     }
353
354     // Best point size was not updated. re-run so the TextFit should be fitted really.
355     if(!bestSizeUpdatedLatest)
356     {
357       impl.SetDefaultLineSize(bestMinLineSize);
358       CheckForTextFit(controller, bestPointSize, layoutSize);
359     }
360
361     model->mElideEnabled              = actualellipsis;
362     impl.mFontDefaults->mFitPointSize = bestPointSize;
363     impl.mFontDefaults->sizeDefined   = true;
364     impl.ClearFontData();
365   }
366 }
367
368 void Controller::Relayouter::FitPointSizeforLayout(Controller& controller, const Size& layoutSize)
369 {
370   Controller::Impl& impl = *controller.mImpl;
371
372   const OperationsMask operations = impl.mOperationsPending;
373   if(NO_OPERATION != (UPDATE_LAYOUT_SIZE & operations) || impl.mTextFitContentSize != layoutSize)
374   {
375     DALI_TRACE_SCOPE(gTraceFilter, "DALI_TEXT_FIT_LAYOUT");
376     ModelPtr& model = impl.mModel;
377
378     bool  actualellipsis         = model->mElideEnabled;
379     float minPointSize           = impl.mTextFitMinSize;
380     float maxPointSize           = impl.mTextFitMaxSize;
381     float pointInterval          = impl.mTextFitStepSize;
382     float currentFitPointSize    = impl.mFontDefaults->mFitPointSize;
383     float currentDefaultLineSize = impl.mLayoutEngine.GetDefaultLineSize();
384     bool  isMultiLine            = impl.mLayoutEngine.GetLayout() == Layout::Engine::MULTI_LINE_BOX;
385     // Instead of using the LineSize of the current TextLabel, the LineSize set in TextFit is used.
386
387     impl.SetDefaultLineSize(impl.mTextFitLineSize);
388
389     model->mElideEnabled = false;
390     float bestPointSize = minPointSize;
391
392     // check zero value
393     if(pointInterval < 1.f)
394     {
395       impl.mTextFitStepSize = pointInterval = 1.0f;
396     }
397
398     uint32_t pointSizeRange = static_cast<uint32_t>(ceil((maxPointSize - minPointSize) / pointInterval));
399
400     if(isMultiLine || pointSizeRange < 3)
401     {
402       // Ensure minPointSize + pointSizeRange * pointInverval >= maxPointSize
403       while(minPointSize + static_cast<float>(pointSizeRange) * pointInterval < maxPointSize)
404       {
405         ++pointSizeRange;
406       }
407
408       uint32_t bestSizeIndex = 0;
409       uint32_t minIndex      = bestSizeIndex + 1u;
410       uint32_t maxIndex      = pointSizeRange + 1u;
411
412       bool bestSizeUpdatedLatest = false;
413       // Find best size as binary search.
414       // Range format as [l r). (left closed, right opened)
415       // It mean, we already check all i < l is valid, and r <= i is invalid.
416       // Below binary search will check m = (l+r)/2 point.
417       // Search area sperate as [l m) or [m+1 r)
418       //
419       // Basically, we can assume that 0 (minPointSize) is always valid.
420       // Now, we will check [1 pointSizeRange] range s.t. pointSizeRange mean the maxPointSize
421       while(minIndex < maxIndex)
422       {
423         uint32_t    testIndex     = minIndex + ((maxIndex - minIndex) >> 1u);
424         const float testPointSize = std::min(maxPointSize, minPointSize + static_cast<float>(testIndex) * pointInterval);
425
426         if(CheckForTextFit(controller, testPointSize, layoutSize))
427         {
428           bestSizeUpdatedLatest = true;
429
430           bestSizeIndex = testIndex;
431           minIndex      = testIndex + 1u;
432         }
433         else
434         {
435           bestSizeUpdatedLatest = false;
436           maxIndex              = testIndex;
437         }
438       }
439       bestPointSize = std::min(maxPointSize, minPointSize + static_cast<float>(bestSizeIndex) * pointInterval);
440
441       // Best point size was not updated. re-run so the TextFit should be fitted really.
442       if(!bestSizeUpdatedLatest)
443       {
444         CheckForTextFit(controller, bestPointSize, layoutSize);
445       }
446     }
447     else
448     {
449       // assume textSize = a * pointSize + b, finding a and b.
450       Size              textSize;
451       TextUpdateInfo&   textUpdateInfo  = impl.mTextUpdateInfo;
452       const OperationsMask onlyOnceOperations = static_cast<OperationsMask>(CONVERT_TO_UTF32 |
453                                                                             GET_SCRIPTS |
454                                                                             VALIDATE_FONTS |
455                                                                             GET_LINE_BREAKS |
456                                                                             BIDI_INFO |
457                                                                             SHAPE_TEXT |
458                                                                             GET_GLYPH_METRICS);
459
460       float resultBasedX[2];
461       float resultBasedY[2];
462       float tmpPointSize[2] = {minPointSize, maxPointSize};
463
464       // Calculate a and b by creating simultaneous equations with two calculations.
465       for(int i=0;i<2;i++)
466       {
467         impl.mFontDefaults->mFitPointSize = tmpPointSize[i];
468         impl.mFontDefaults->sizeDefined = true;
469         impl.ClearFontData();
470
471         textUpdateInfo.mParagraphCharacterIndex     = 0u;
472         textUpdateInfo.mRequestedNumberOfCharacters = impl.mModel->mLogicalModel->mText.Count();
473
474
475         // Make sure the model is up-to-date before layouting
476         impl.UpdateModel(onlyOnceOperations);
477
478         DoRelayout(impl,
479                   Size(layoutSize.width, MAX_FLOAT),
480                   static_cast<OperationsMask>(onlyOnceOperations | LAYOUT),
481                   textSize);
482
483         // Clear the update info. This info will be set the next time the text is updated.
484         textUpdateInfo.Clear();
485         textUpdateInfo.mClearAll = true;
486
487         resultBasedX[i] = textSize.x;
488         resultBasedY[i] = textSize.y;
489       }
490
491       float aBasedX = (resultBasedX[1] - resultBasedX[0]) / (tmpPointSize[1] - tmpPointSize[0]);
492       float bBasedX = resultBasedX[1] - aBasedX * tmpPointSize[1];
493       aBasedX = std::max(aBasedX, Dali::Math::MACHINE_EPSILON_1000);
494
495       float aBasedY = (resultBasedY[1] - resultBasedY[0]) / (tmpPointSize[1] - tmpPointSize[0]);
496       float bBasedY = resultBasedY[1] - aBasedY * tmpPointSize[1];
497       aBasedY = std::max(aBasedY, Dali::Math::MACHINE_EPSILON_1000);
498
499       float bestPointSizeBasedX = (layoutSize.x - bBasedX) / aBasedX;
500       float bestPointSizeBasedY = (layoutSize.y - bBasedY) / aBasedY;
501
502       bestPointSize = std::min(bestPointSizeBasedX, bestPointSizeBasedY);
503       bestPointSize = std::min(std::max(bestPointSize, minPointSize), maxPointSize);
504       bestPointSize = std::floor((bestPointSize - minPointSize) / pointInterval) * pointInterval + minPointSize;
505
506       if(CheckForTextFit(controller, bestPointSize, layoutSize))
507       {
508         while(bestPointSize + pointInterval <= maxPointSize && CheckForTextFit(controller, bestPointSize + pointInterval, layoutSize))
509         {
510           bestPointSize += pointInterval;
511         }
512       }
513       else if(bestPointSize - pointInterval >= minPointSize)
514       {
515         do
516         {
517           bestPointSize -= pointInterval;
518         } while(bestPointSize - pointInterval >= minPointSize && !CheckForTextFit(controller, bestPointSize, layoutSize));
519       }
520     }
521
522     model->mElideEnabled = actualellipsis;
523     if(!Dali::Equals(currentFitPointSize, bestPointSize))
524     {
525       impl.mTextFitChanged = true;
526     }
527     // Revert back to the original TextLabel LineSize.
528     impl.SetDefaultLineSize(currentDefaultLineSize);
529     impl.mFontDefaults->mFitPointSize = bestPointSize;
530     impl.mFontDefaults->sizeDefined   = true;
531     impl.ClearFontData();
532   }
533 }
534
535 float Controller::Relayouter::GetHeightForWidth(Controller& controller, float width)
536 {
537   DALI_LOG_INFO(gLogFilter, Debug::Verbose, "-->Controller::GetHeightForWidth %p width %f\n", &controller, width);
538   DALI_TRACE_SCOPE(gTraceFilter, "DALI_TEXT_GET_HEIGHT_FOR_WIDTH");
539
540   // Make sure the model is up-to-date before layouting
541   EventHandler::ProcessModifyEvents(controller);
542
543   Controller::Impl& impl           = *controller.mImpl;
544   ModelPtr&         model          = impl.mModel;
545   VisualModelPtr&   visualModel    = model->mVisualModel;
546   TextUpdateInfo&   textUpdateInfo = impl.mTextUpdateInfo;
547
548   // Get cached value.
549   Size layoutSize = visualModel->GetHeightForWidth();
550
551   if(fabsf(width - layoutSize.width) > Math::MACHINE_EPSILON_1000 ||
552      textUpdateInfo.mFullRelayoutNeeded ||
553      textUpdateInfo.mClearAll)
554   {
555     // Layout the text for the new width.
556     OperationsMask requestedOperationsMask        = static_cast<OperationsMask>(LAYOUT);
557     Size           sizeRequestedWidthAndMaxHeight = Size(width, MAX_FLOAT);
558
559     layoutSize = CalculateLayoutSizeOnRequiredControllerSize(controller, sizeRequestedWidthAndMaxHeight, requestedOperationsMask);
560
561     // The calculated layout width may not be the same as the requested width.
562     // For cache efficiency, the requested width is stored.
563     layoutSize.width = width;
564     visualModel->SetHeightForWidth(layoutSize);
565
566     DALI_LOG_INFO(gLogFilter, Debug::Verbose, "<--Controller::GetHeightForWidth calculated %f\n", layoutSize.height);
567   }
568   else
569   {
570     DALI_LOG_INFO(gLogFilter, Debug::Verbose, "<--Controller::GetHeightForWidth cached %f\n", layoutSize.height);
571   }
572
573   return layoutSize.height;
574 }
575
576 Controller::UpdateTextType Controller::Relayouter::Relayout(Controller& controller, const Size& size, Dali::LayoutDirection::Type layoutDirection)
577 {
578   Controller::Impl& impl           = *controller.mImpl;
579   ModelPtr&         model          = impl.mModel;
580   VisualModelPtr&   visualModel    = model->mVisualModel;
581   TextUpdateInfo&   textUpdateInfo = impl.mTextUpdateInfo;
582
583   DALI_LOG_INFO(gLogFilter, Debug::Verbose, "-->Controller::Relayout %p size %f,%f, autoScroll[%s]\n", &controller, size.width, size.height, impl.mIsAutoScrollEnabled ? "true" : "false");
584   DALI_TRACE_SCOPE(gTraceFilter, "DALI_TEXT_RELAYOUT");
585
586   UpdateTextType updateTextType = NONE_UPDATED;
587
588   if((size.width < Math::MACHINE_EPSILON_1000) || (size.height < Math::MACHINE_EPSILON_1000))
589   {
590     if(0u != visualModel->mGlyphPositions.Count())
591     {
592       visualModel->mGlyphPositions.Clear();
593       updateTextType = MODEL_UPDATED;
594     }
595
596     // Clear the update info. This info will be set the next time the text is updated.
597     textUpdateInfo.Clear();
598
599     // Not worth to relayout if width or height is equal to zero.
600     DALI_LOG_INFO(gLogFilter, Debug::Verbose, "<--Controller::Relayout (skipped)\n");
601
602     return updateTextType;
603   }
604
605   // Whether a new size has been set.
606   const bool newSize = (size != visualModel->mControlSize);
607
608   // Get a reference to the pending operations member
609   OperationsMask& operationsPending = impl.mOperationsPending;
610
611   if(newSize)
612   {
613     DALI_LOG_INFO(gLogFilter, Debug::Verbose, "new size (previous size %f,%f)\n", visualModel->mControlSize.width, visualModel->mControlSize.height);
614
615     if((0 == textUpdateInfo.mNumberOfCharactersToAdd) &&
616        (0 == textUpdateInfo.mPreviousNumberOfCharacters) &&
617        ((visualModel->mControlSize.width < Math::MACHINE_EPSILON_1000) || (visualModel->mControlSize.height < Math::MACHINE_EPSILON_1000)))
618     {
619       textUpdateInfo.mNumberOfCharactersToAdd = model->mLogicalModel->mText.Count();
620     }
621
622     // Layout operations that need to be done if the size changes.
623     operationsPending = static_cast<OperationsMask>(operationsPending |
624                                                     LAYOUT |
625                                                     ALIGN |
626                                                     UPDATE_LAYOUT_SIZE |
627                                                     REORDER);
628     // Set the update info to relayout the whole text.
629     textUpdateInfo.mFullRelayoutNeeded = true;
630     textUpdateInfo.mCharacterIndex     = 0u;
631
632     // Store the size used to layout the text.
633     visualModel->mControlSize = size;
634   }
635
636   // Whether there are modify events.
637   if(0u != impl.mModifyEvents.Count())
638   {
639     // Style operations that need to be done if the text is modified.
640     operationsPending = static_cast<OperationsMask>(operationsPending | COLOR);
641   }
642
643   // Set the update info to elide the text.
644   if(model->mElideEnabled ||
645      ((NULL != impl.mEventData) && impl.mEventData->mIsPlaceholderElideEnabled))
646   {
647     // Update Text layout for applying elided
648     operationsPending                  = static_cast<OperationsMask>(operationsPending |
649                                                     ALIGN |
650                                                     LAYOUT |
651                                                     UPDATE_LAYOUT_SIZE |
652                                                     REORDER);
653     textUpdateInfo.mFullRelayoutNeeded = true;
654     textUpdateInfo.mCharacterIndex     = 0u;
655   }
656
657   bool layoutDirectionChanged = false;
658   if(impl.mLayoutDirection != layoutDirection)
659   {
660     // Flag to indicate that the layout direction has changed.
661     layoutDirectionChanged = true;
662     // Clear the update info. This info will be set the next time the text is updated.
663     textUpdateInfo.mClearAll = true;
664     // Apply modifications to the model
665     // Shape the text again is needed because characters like '()[]{}' have to be mirrored and the glyphs generated again.
666     operationsPending     = static_cast<OperationsMask>(operationsPending |
667                                                     GET_GLYPH_METRICS |
668                                                     SHAPE_TEXT |
669                                                     UPDATE_DIRECTION |
670                                                     ALIGN |
671                                                     LAYOUT |
672                                                     BIDI_INFO |
673                                                     REORDER);
674     impl.mLayoutDirection = layoutDirection;
675   }
676
677   // Make sure the model is up-to-date before layouting.
678   EventHandler::ProcessModifyEvents(controller);
679   bool updated = impl.UpdateModel(operationsPending);
680
681   // Layout the text.
682   Size layoutSize;
683   updated = DoRelayout(impl, size, operationsPending, layoutSize) || updated;
684
685   if(updated)
686   {
687     updateTextType = MODEL_UPDATED;
688   }
689
690   // Do not re-do any operation until something changes.
691   operationsPending          = NO_OPERATION;
692   model->mScrollPositionLast = model->mScrollPosition;
693
694   // Whether the text control is editable
695   const bool isEditable = NULL != impl.mEventData;
696
697   // Keep the current offset as it will be used to update the decorator's positions (if the size changes).
698   Vector2 offset;
699   if(newSize && isEditable)
700   {
701     offset = model->mScrollPosition;
702   }
703
704   if(!isEditable || !controller.IsMultiLineEnabled())
705   {
706     // After doing the text layout, the vertical offset to place the actor in the desired position can be calculated.
707     CalculateVerticalOffset(impl, size);
708   }
709   else // TextEditor
710   {
711     // If layoutSize is bigger than size, vertical align has no meaning.
712     if(layoutSize.y < size.y)
713     {
714       CalculateVerticalOffset(impl, size);
715       if(impl.mEventData)
716       {
717         impl.mEventData->mScrollAfterDelete = false;
718       }
719     }
720   }
721
722   if(isEditable)
723   {
724     if(newSize || layoutDirectionChanged)
725     {
726       // If there is a new size or layout direction is changed, the scroll position needs to be clamped.
727       impl.ClampHorizontalScroll(layoutSize);
728
729       // Update the decorator's positions is needed if there is a new size.
730       impl.mEventData->mDecorator->UpdatePositions(model->mScrollPosition - offset);
731
732       // All decorator elements need to be updated.
733       if(EventData::IsEditingState(impl.mEventData->mState))
734       {
735         impl.mEventData->mScrollAfterUpdatePosition = true;
736         impl.mEventData->mUpdateCursorPosition      = true;
737         impl.mEventData->mUpdateGrabHandlePosition  = true;
738       }
739       else if(impl.mEventData->mState == EventData::SELECTING)
740       {
741         impl.mEventData->mUpdateHighlightBox = true;
742       }
743     }
744
745     // Move the cursor, grab handle etc.
746     if(impl.ProcessInputEvents())
747     {
748       updateTextType = static_cast<UpdateTextType>(updateTextType | DECORATOR_UPDATED);
749     }
750   }
751
752   // Clear the update info. This info will be set the next time the text is updated.
753   textUpdateInfo.Clear();
754   DALI_LOG_INFO(gLogFilter, Debug::Verbose, "<--Controller::Relayout\n");
755
756   return updateTextType;
757 }
758
759 bool Controller::Relayouter::DoRelayout(Controller::Impl& impl, const Size& size, OperationsMask operationsRequired, Size& layoutSize)
760 {
761   bool layoutTooSmall = false;
762   return DoRelayout(impl, size, operationsRequired, layoutSize, layoutTooSmall);
763 }
764
765 bool Controller::Relayouter::DoRelayout(Controller::Impl& impl, const Size& size, OperationsMask operationsRequired, Size& layoutSize, bool& layoutTooSmall)
766 {
767   DALI_LOG_INFO(gLogFilter, Debug::Verbose, "-->Controller::Relayouter::DoRelayout %p size %f,%f\n", &impl, size.width, size.height);
768   DALI_TRACE_SCOPE(gTraceFilter2, "DALI_TEXT_DORELAYOUT");
769   bool viewUpdated(false);
770
771   // Calculate the operations to be done.
772   const OperationsMask operations = static_cast<OperationsMask>(impl.mOperationsPending & operationsRequired);
773
774   TextUpdateInfo&      textUpdateInfo              = impl.mTextUpdateInfo;
775   const CharacterIndex startIndex                  = textUpdateInfo.mParagraphCharacterIndex;
776   const Length         requestedNumberOfCharacters = textUpdateInfo.mRequestedNumberOfCharacters;
777
778   // Get the current layout size.
779   VisualModelPtr& visualModel = impl.mModel->mVisualModel;
780   layoutSize                  = visualModel->GetLayoutSize();
781
782   if(NO_OPERATION != (LAYOUT & operations))
783   {
784     DALI_LOG_INFO(gLogFilter, Debug::Verbose, "-->Controller::DoRelayout LAYOUT & operations\n");
785
786     // Some vectors with data needed to layout and reorder may be void
787     // after the first time the text has been laid out.
788     // Fill the vectors again.
789
790     // Calculate the number of glyphs to layout.
791     const Vector<GlyphIndex>& charactersToGlyph        = visualModel->mCharactersToGlyph;
792     const Vector<Length>&     glyphsPerCharacter       = visualModel->mGlyphsPerCharacter;
793     const GlyphIndex* const   charactersToGlyphBuffer  = charactersToGlyph.Begin();
794     const Length* const       glyphsPerCharacterBuffer = glyphsPerCharacter.Begin();
795
796     const CharacterIndex lastIndex       = startIndex + ((requestedNumberOfCharacters > 0u) ? requestedNumberOfCharacters - 1u : 0u);
797     const GlyphIndex     startGlyphIndex = textUpdateInfo.mStartGlyphIndex;
798
799     // Make sure the index is not out of bound
800     if(charactersToGlyph.Count() != glyphsPerCharacter.Count() ||
801        requestedNumberOfCharacters > charactersToGlyph.Count() ||
802        (lastIndex > charactersToGlyph.Count() && charactersToGlyph.Count() > 0u))
803     {
804       std::string currentText;
805       impl.GetText(currentText);
806
807       DALI_LOG_ERROR("Controller::DoRelayout: Attempting to access invalid buffer\n");
808       DALI_LOG_ERROR("Current text is: %s\n", currentText.c_str());
809       DALI_LOG_ERROR("startIndex: %u, lastIndex: %u, requestedNumberOfCharacters: %u, charactersToGlyph.Count = %lu, glyphsPerCharacter.Count = %lu\n", startIndex, lastIndex, requestedNumberOfCharacters, charactersToGlyph.Count(), glyphsPerCharacter.Count());
810
811       return false;
812     }
813
814     const Length numberOfGlyphs      = (requestedNumberOfCharacters > 0u) ? *(charactersToGlyphBuffer + lastIndex) + *(glyphsPerCharacterBuffer + lastIndex) - startGlyphIndex : 0u;
815     const Length totalNumberOfGlyphs = visualModel->mGlyphs.Count();
816
817     if(0u == totalNumberOfGlyphs)
818     {
819       if(NO_OPERATION != (UPDATE_LAYOUT_SIZE & operations))
820       {
821         visualModel->SetLayoutSize(Size::ZERO);
822       }
823
824       // Nothing else to do if there is no glyphs.
825       DALI_LOG_INFO(gLogFilter, Debug::Verbose, "<--Controller::DoRelayout no glyphs, view updated true\n");
826       return true;
827     }
828
829     // Set the layout parameters.
830     Layout::Parameters layoutParameters(size, impl.mModel);
831
832     // Resize the vector of positions to have the same size than the vector of glyphs.
833     Vector<Vector2>& glyphPositions = visualModel->mGlyphPositions;
834     glyphPositions.Resize(totalNumberOfGlyphs);
835
836     // Whether the last character is a new paragraph character.
837     const Character* const textBuffer           = impl.mModel->mLogicalModel->mText.Begin();
838     textUpdateInfo.mIsLastCharacterNewParagraph = TextAbstraction::IsNewParagraph(*(textBuffer + (impl.mModel->mLogicalModel->mText.Count() - 1u)));
839     layoutParameters.isLastNewParagraph         = textUpdateInfo.mIsLastCharacterNewParagraph;
840
841     // The initial glyph and the number of glyphs to layout.
842     layoutParameters.startGlyphIndex        = startGlyphIndex;
843     layoutParameters.numberOfGlyphs         = numberOfGlyphs;
844     layoutParameters.startLineIndex         = textUpdateInfo.mStartLineIndex;
845     layoutParameters.estimatedNumberOfLines = textUpdateInfo.mEstimatedNumberOfLines;
846
847     // Update the ellipsis
848     bool elideTextEnabled = impl.mModel->mElideEnabled;
849     auto ellipsisPosition = impl.mModel->mEllipsisPosition;
850
851     if(NULL != impl.mEventData)
852     {
853       if(impl.mEventData->mPlaceholderEllipsisFlag && impl.IsShowingPlaceholderText())
854       {
855         elideTextEnabled = impl.mEventData->mIsPlaceholderElideEnabled;
856       }
857       else if(EventData::INACTIVE != impl.mEventData->mState)
858       {
859         // Disable ellipsis when editing
860         elideTextEnabled = false;
861       }
862
863       // Reset the scroll position in inactive state
864       if(elideTextEnabled && (impl.mEventData->mState == EventData::INACTIVE))
865       {
866         impl.ResetScrollPosition();
867       }
868     }
869
870     // Update the visual model.
871     bool isAutoScrollEnabled            = impl.mIsAutoScrollEnabled;
872     bool isAutoScrollMaxTextureExceeded = impl.mIsAutoScrollMaxTextureExceeded;
873     bool isHiddenInputEnabled           = false;
874     if(impl.mHiddenInput && impl.mEventData != nullptr && impl.mHiddenInput->GetHideMode() != Toolkit::HiddenInput::Mode::HIDE_NONE)
875     {
876       isHiddenInputEnabled = true;
877     }
878
879     Size newLayoutSize;
880     viewUpdated               = impl.mLayoutEngine.LayoutText(layoutParameters,
881                                                 newLayoutSize,
882                                                 elideTextEnabled,
883                                                 isAutoScrollEnabled,
884                                                 isAutoScrollMaxTextureExceeded,
885                                                 isHiddenInputEnabled,
886                                                 ellipsisPosition);
887     impl.mIsAutoScrollEnabled = isAutoScrollEnabled;
888     layoutTooSmall = !viewUpdated;
889
890     viewUpdated = viewUpdated || (newLayoutSize != layoutSize);
891
892     if(viewUpdated)
893     {
894       layoutSize = newLayoutSize;
895
896       if(NO_OPERATION != (UPDATE_DIRECTION & operations))
897       {
898         impl.mIsTextDirectionRTL = false;
899       }
900
901       if((NO_OPERATION != (UPDATE_DIRECTION & operations)) && !visualModel->mLines.Empty())
902       {
903         impl.mIsTextDirectionRTL = visualModel->mLines[0u].direction;
904       }
905
906       // Sets the layout size.
907       if(NO_OPERATION != (UPDATE_LAYOUT_SIZE & operations))
908       {
909         visualModel->SetLayoutSize(layoutSize);
910       }
911     } // view updated
912   }
913
914   if(NO_OPERATION != (ALIGN & operations))
915   {
916     DoRelayoutHorizontalAlignment(impl, size, startIndex, requestedNumberOfCharacters);
917     viewUpdated = true;
918   }
919 #if defined(DEBUG_ENABLED)
920   std::string currentText;
921   impl.GetText(currentText);
922   DALI_LOG_INFO(gLogFilter, Debug::Concise, "Controller::Relayouter::DoRelayout [%p] mImpl->mIsTextDirectionRTL[%s] [%s]\n", &impl, (impl.mIsTextDirectionRTL) ? "true" : "false", currentText.c_str());
923 #endif
924   DALI_LOG_INFO(gLogFilter, Debug::Verbose, "<--Controller::Relayouter::DoRelayout, view updated %s\n", (viewUpdated ? "true" : "false"));
925   DALI_LOG_INFO(gLogFilter, Debug::Verbose, "<--Controller::Relayouter::DoRelayout, layout too small %s\n", (layoutTooSmall ? "true" : "false"));
926   return viewUpdated;
927 }
928
929 void Controller::Relayouter::DoRelayoutHorizontalAlignment(Controller::Impl&    impl,
930                                                            const Size&          size,
931                                                            const CharacterIndex startIndex,
932                                                            const Length         requestedNumberOfCharacters)
933 {
934   // The visualModel
935   VisualModelPtr& visualModel = impl.mModel->mVisualModel;
936
937   // The laid-out lines.
938   Vector<LineRun>& lines = visualModel->mLines;
939
940   CharacterIndex alignStartIndex                  = startIndex;
941   Length         alignRequestedNumberOfCharacters = requestedNumberOfCharacters;
942
943   // the whole text needs to be full aligned.
944   // If you do not do a full aligned, only the last line of the multiline input is aligned.
945   if(impl.mEventData && impl.mEventData->mUpdateAlignment)
946   {
947     alignStartIndex                   = 0u;
948     alignRequestedNumberOfCharacters  = impl.mModel->mLogicalModel->mText.Count();
949     impl.mEventData->mUpdateAlignment = false;
950   }
951
952   // If there is no BoundedParagraphRuns then apply the alignment of controller.
953   // Check whether the layout is single line. It's needed to apply one alignment for single-line.
954   // In single-line layout case we need to check whether to follow the alignment of controller or the first BoundedParagraph.
955   // Apply BoundedParagraph's alignment if and only if there is one BoundedParagraph contains all characters. Otherwise follow controller's alignment.
956   const bool isFollowControllerAlignment = ((impl.mModel->GetNumberOfBoundedParagraphRuns() == 0u) ||
957                                             ((Layout::Engine::SINGLE_LINE_BOX == impl.mLayoutEngine.GetLayout()) &&
958                                              (impl.mModel->GetBoundedParagraphRuns()[0].characterRun.numberOfCharacters != impl.mModel->mLogicalModel->mText.Count())));
959
960   if(isFollowControllerAlignment)
961   {
962     // Need to align with the control's size as the text may contain lines
963     // starting either with left to right text or right to left.
964     impl.mLayoutEngine.Align(size,
965                              alignStartIndex,
966                              alignRequestedNumberOfCharacters,
967                              impl.mModel->mHorizontalAlignment,
968                              lines,
969                              impl.mModel->mAlignmentOffset,
970                              impl.mLayoutDirection,
971                              (impl.mModel->mMatchLayoutDirection != DevelText::MatchLayoutDirection::CONTENTS));
972   }
973   else
974   {
975     //Override the controller horizontal-alignment by horizontal-alignment of bounded paragraph.
976     const Length&                      numberOfBoundedParagraphRuns = impl.mModel->GetNumberOfBoundedParagraphRuns();
977     const Vector<BoundedParagraphRun>& boundedParagraphRuns         = impl.mModel->GetBoundedParagraphRuns();
978     const CharacterIndex               alignEndIndex                = alignStartIndex + alignRequestedNumberOfCharacters - 1u;
979
980     Length alignIndex               = alignStartIndex;
981     Length boundedParagraphRunIndex = 0u;
982
983     while(alignIndex <= alignEndIndex && boundedParagraphRunIndex < numberOfBoundedParagraphRuns)
984     {
985       //BP: BoundedParagraph
986       const BoundedParagraphRun& boundedParagraphRun   = boundedParagraphRuns[boundedParagraphRunIndex];
987       const CharacterIndex&      characterStartIndexBP = boundedParagraphRun.characterRun.characterIndex;
988       const Length&              numberOfCharactersBP  = boundedParagraphRun.characterRun.numberOfCharacters;
989       const CharacterIndex       characterEndIndexBP   = characterStartIndexBP + numberOfCharactersBP - 1u;
990
991       CharacterIndex                  decidedAlignStartIndex         = alignIndex;
992       Length                          decidedAlignNumberOfCharacters = alignEndIndex - alignIndex + 1u;
993       Text::HorizontalAlignment::Type decidedHorizontalAlignment     = impl.mModel->mHorizontalAlignment;
994
995       /*
996          * Shortcuts to explain indexes cases:
997          *
998          * AS: Alignment Start Index
999          * AE: Alignment End Index
1000          * PS: Paragraph Start Index
1001          * PE: Paragraph End Index
1002          * B: BoundedParagraph Alignment
1003          * M: Model Alignment
1004          *
1005          */
1006
1007       if(alignIndex < characterStartIndexBP && characterStartIndexBP <= alignEndIndex) /// AS.MMMMMM.PS--------AE
1008       {
1009         // Alignment from "Alignment Start Index" to index before "Paragraph Start Index" according to "Model Alignment"
1010         decidedAlignStartIndex         = alignIndex;
1011         decidedAlignNumberOfCharacters = characterStartIndexBP - alignIndex;
1012         decidedHorizontalAlignment     = impl.mModel->mHorizontalAlignment;
1013
1014         // Need to re-heck the case of current bounded paragraph
1015         alignIndex = characterStartIndexBP; // Shift AS to be PS
1016       }
1017       else if((characterStartIndexBP <= alignIndex && alignIndex <= characterEndIndexBP) ||     /// ---PS.BBBBBBB.AS.BBBBBBB.PE---
1018               (characterStartIndexBP <= alignEndIndex && alignEndIndex <= characterEndIndexBP)) /// ---PS.BBBBBB.AE.BBBBBBB.PE---
1019       {
1020         // Alignment from "Paragraph Start Index" to "Paragraph End Index" according to "BoundedParagraph Alignment"
1021         decidedAlignStartIndex         = characterStartIndexBP;
1022         decidedAlignNumberOfCharacters = numberOfCharactersBP;
1023         decidedHorizontalAlignment     = boundedParagraphRun.horizontalAlignmentDefined ? boundedParagraphRun.horizontalAlignment : impl.mModel->mHorizontalAlignment;
1024
1025         alignIndex = characterEndIndexBP + 1u; // Shift AS to be after PE direct
1026         boundedParagraphRunIndex++;            // Align then check the case of next bounded paragraph
1027       }
1028       else
1029       {
1030         boundedParagraphRunIndex++; // Check the case of next bounded paragraph
1031         continue;
1032       }
1033
1034       impl.mLayoutEngine.Align(size,
1035                                decidedAlignStartIndex,
1036                                decidedAlignNumberOfCharacters,
1037                                decidedHorizontalAlignment,
1038                                lines,
1039                                impl.mModel->mAlignmentOffset,
1040                                impl.mLayoutDirection,
1041                                (impl.mModel->mMatchLayoutDirection != DevelText::MatchLayoutDirection::CONTENTS));
1042     }
1043
1044     //Align the remaining that is not aligned
1045     if(alignIndex <= alignEndIndex)
1046     {
1047       impl.mLayoutEngine.Align(size,
1048                                alignIndex,
1049                                (alignEndIndex - alignIndex + 1u),
1050                                impl.mModel->mHorizontalAlignment,
1051                                lines,
1052                                impl.mModel->mAlignmentOffset,
1053                                impl.mLayoutDirection,
1054                                (impl.mModel->mMatchLayoutDirection != DevelText::MatchLayoutDirection::CONTENTS));
1055     }
1056   }
1057 }
1058
1059 void Controller::Relayouter::CalculateVerticalOffset(Controller::Impl& impl, const Size& controlSize)
1060 {
1061   ModelPtr&       model                 = impl.mModel;
1062   VisualModelPtr& visualModel           = model->mVisualModel;
1063   Size            layoutSize            = model->mVisualModel->GetLayoutSize();
1064   Size            oldLayoutSize         = layoutSize;
1065   float           offsetY               = 0.f;
1066   bool            needRecalc            = false;
1067   float           defaultFontLineHeight = impl.GetDefaultFontLineHeight();
1068
1069   if(fabsf(layoutSize.height) < Math::MACHINE_EPSILON_1000)
1070   {
1071     // Get the line height of the default font.
1072     layoutSize.height = defaultFontLineHeight;
1073   }
1074
1075   // Whether the text control is editable
1076   const bool isEditable = NULL != impl.mEventData;
1077   if(isEditable && !Dali::Equals(layoutSize.height, defaultFontLineHeight) && impl.IsShowingPlaceholderText())
1078   {
1079     // This code prevents the wrong positioning of cursor when the layout size is bigger/smaller than defaultFontLineHeight.
1080     // This situation occurs when the size of placeholder text is different from the default text.
1081     layoutSize.height = defaultFontLineHeight;
1082     needRecalc        = true;
1083   }
1084
1085   switch(model->mVerticalAlignment)
1086   {
1087     case VerticalAlignment::TOP:
1088     {
1089       model->mScrollPosition.y = 0.f;
1090       offsetY                  = 0.f;
1091       break;
1092     }
1093     case VerticalAlignment::CENTER:
1094     {
1095       model->mScrollPosition.y = floorf(0.5f * (controlSize.height - layoutSize.height)); // try to avoid pixel alignment.
1096       if(needRecalc) offsetY = floorf(0.5f * (layoutSize.height - oldLayoutSize.height));
1097       break;
1098     }
1099     case VerticalAlignment::BOTTOM:
1100     {
1101       model->mScrollPosition.y = controlSize.height - layoutSize.height;
1102       if(needRecalc) offsetY = layoutSize.height - oldLayoutSize.height;
1103       break;
1104     }
1105   }
1106
1107   if(needRecalc)
1108   {
1109     // Update glyphPositions according to recalculation.
1110     const Length     positionCount  = visualModel->mGlyphPositions.Count();
1111     Vector<Vector2>& glyphPositions = visualModel->mGlyphPositions;
1112     for(Length index = 0u; index < positionCount; index++)
1113     {
1114       glyphPositions[index].y += offsetY;
1115     }
1116   }
1117 }
1118
1119 } // namespace Text
1120
1121 } // namespace Toolkit
1122
1123 } // namespace Dali