Merge "It may be outside the range of type int. Change to uint64_t." into devel/master
[platform/core/uifw/dali-toolkit.git] / dali-toolkit / internal / text / 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/text-controller-relayouter.h>
20
21 // EXTERNAL INCLUDES
22 #include <dali/integration-api/debug.h>
23 #include <limits>
24
25 // INTERNAL INCLUDES
26 #include <dali-toolkit/internal/text/layouts/layout-parameters.h>
27 #include <dali-toolkit/internal/text/text-controller-event-handler.h>
28 #include <dali-toolkit/internal/text/text-controller-impl.h>
29
30 namespace
31 {
32 #if defined(DEBUG_ENABLED)
33 Debug::Filter* gLogFilter = Debug::Filter::New(Debug::NoLogging, true, "LOG_TEXT_CONTROLS");
34 #endif
35
36 constexpr float MAX_FLOAT = std::numeric_limits<float>::max();
37
38 float ConvertToEven(float value)
39 {
40   int intValue(static_cast<int>(value));
41   return static_cast<float>(intValue + (intValue & 1));
42 }
43
44 } // namespace
45
46 namespace Dali
47 {
48 namespace Toolkit
49 {
50 namespace Text
51 {
52 Size Controller::Relayouter::CalculateLayoutSizeOnRequiredControllerSize(Controller& controller, const Size& requestedControllerSize, const OperationsMask& requestedOperationsMask)
53 {
54   DALI_LOG_INFO(gLogFilter, Debug::Verbose, "-->CalculateLayoutSizeOnRequiredControllerSize\n");
55   Size calculatedLayoutSize;
56
57   Controller::Impl& impl        = *controller.mImpl;
58   ModelPtr&         model       = impl.mModel;
59   VisualModelPtr&   visualModel = model->mVisualModel;
60
61   // Operations that can be done only once until the text changes.
62   const OperationsMask onlyOnceOperations = static_cast<OperationsMask>(CONVERT_TO_UTF32 |
63                                                                         GET_SCRIPTS |
64                                                                         VALIDATE_FONTS |
65                                                                         GET_LINE_BREAKS |
66                                                                         BIDI_INFO |
67                                                                         SHAPE_TEXT |
68                                                                         GET_GLYPH_METRICS);
69
70   const OperationsMask sizeOperations = static_cast<OperationsMask>(LAYOUT | ALIGN | REORDER);
71
72   // Set the update info to relayout the whole text.
73   TextUpdateInfo& textUpdateInfo = impl.mTextUpdateInfo;
74   if((0 == textUpdateInfo.mNumberOfCharactersToAdd) &&
75      (0 == textUpdateInfo.mPreviousNumberOfCharacters) &&
76      ((visualModel->mControlSize.width < Math::MACHINE_EPSILON_1000) || (visualModel->mControlSize.height < Math::MACHINE_EPSILON_1000)))
77   {
78     textUpdateInfo.mNumberOfCharactersToAdd = model->mLogicalModel->mText.Count();
79   }
80   textUpdateInfo.mParagraphCharacterIndex     = 0u;
81   textUpdateInfo.mRequestedNumberOfCharacters = model->mLogicalModel->mText.Count();
82
83   // Get a reference to the pending operations member
84   OperationsMask& operationsPending = impl.mOperationsPending;
85
86   // Store the actual control's size to restore later.
87   const Size actualControlSize = visualModel->mControlSize;
88
89   // Whether the text control is editable
90   const bool isEditable = NULL != impl.mEventData;
91
92   if(!isEditable)
93   {
94     impl.UpdateModel(onlyOnceOperations);
95
96     // Layout the text for the new width.
97     operationsPending = static_cast<OperationsMask>(operationsPending | requestedOperationsMask);
98
99     DoRelayout(impl,
100                requestedControllerSize,
101                static_cast<OperationsMask>(onlyOnceOperations | requestedOperationsMask),
102                calculatedLayoutSize);
103
104     textUpdateInfo.Clear();
105     textUpdateInfo.mClearAll = true;
106
107     // Do not do again the only once operations.
108     operationsPending = static_cast<OperationsMask>(operationsPending & ~onlyOnceOperations);
109   }
110   else
111   {
112     // This is to keep Index to the first character to be updated.
113     // Then restore it after calling Clear method.
114     auto updateInfoCharIndexBackup = textUpdateInfo.mCharacterIndex;
115
116     // Layout the text for the new width.
117     // Apply the pending operations, requested operations and the only once operations.
118     // Then remove onlyOnceOperations
119     operationsPending = static_cast<OperationsMask>(operationsPending | requestedOperationsMask | onlyOnceOperations);
120
121     // Make sure the model is up-to-date before layouting
122     impl.UpdateModel(static_cast<OperationsMask>(operationsPending & ~UPDATE_LAYOUT_SIZE));
123
124     DoRelayout(impl,
125                requestedControllerSize,
126                static_cast<OperationsMask>(operationsPending & ~UPDATE_LAYOUT_SIZE),
127                calculatedLayoutSize);
128
129     // Clear the update info. This info will be set the next time the text is updated.
130     textUpdateInfo.Clear();
131
132     //TODO: Refactor "DoRelayout" and extract common code of size calculation without modifying attributes of mVisualModel,
133     //TODO: then calculate GlyphPositions. Lines, Size, Layout for Natural-Size
134     //TODO: and utilize the values in OperationsPending and TextUpdateInfo without changing the original one.
135     //TODO: Also it will improve performance because there is no need todo FullRelyout on the next need for layouting.
136
137     // FullRelayoutNeeded should be true because DoRelayout is MAX_FLOAT, MAX_FLOAT.
138     // By this no need to take backup and restore it.
139     textUpdateInfo.mFullRelayoutNeeded = true;
140
141     // Restore mCharacterIndex. Because "Clear" set it to the maximum integer.
142     // The "CalculateTextUpdateIndices" does not work proprely because the mCharacterIndex will be greater than mPreviousNumberOfCharacters.
143     // Which apply an assumption to update only the last  paragraph. That could cause many of out of index crashes.
144     textUpdateInfo.mCharacterIndex = updateInfoCharIndexBackup;
145   }
146
147   // Do the size related operations again.
148   operationsPending = static_cast<OperationsMask>(operationsPending | sizeOperations);
149
150   // Restore the actual control's size.
151   visualModel->mControlSize = actualControlSize;
152
153   return calculatedLayoutSize;
154 }
155
156 Vector3 Controller::Relayouter::GetNaturalSize(Controller& controller)
157 {
158   DALI_LOG_INFO(gLogFilter, Debug::Verbose, "-->Controller::GetNaturalSize\n");
159   Vector3 naturalSizeVec3;
160
161   // Make sure the model is up-to-date before layouting
162   EventHandler::ProcessModifyEvents(controller);
163
164   Controller::Impl& impl        = *controller.mImpl;
165   ModelPtr&         model       = impl.mModel;
166   VisualModelPtr&   visualModel = model->mVisualModel;
167
168   if(impl.mRecalculateNaturalSize)
169   {
170     Size naturalSize;
171
172     // Layout the text for the new width.
173     OperationsMask requestedOperationsMask  = static_cast<OperationsMask>(LAYOUT | REORDER);
174     Size           sizeMaxWidthAndMaxHeight = Size(MAX_FLOAT, MAX_FLOAT);
175
176     naturalSize = CalculateLayoutSizeOnRequiredControllerSize(controller, sizeMaxWidthAndMaxHeight, requestedOperationsMask);
177
178     // Stores the natural size to avoid recalculate it again
179     // unless the text/style changes.
180     visualModel->SetNaturalSize(naturalSize);
181     naturalSizeVec3 = naturalSize;
182
183     impl.mRecalculateNaturalSize = false;
184
185     DALI_LOG_INFO(gLogFilter, Debug::Verbose, "<--Controller::GetNaturalSize calculated %f,%f,%f\n", naturalSizeVec3.x, naturalSizeVec3.y, naturalSizeVec3.z);
186   }
187   else
188   {
189     naturalSizeVec3 = visualModel->GetNaturalSize();
190
191     DALI_LOG_INFO(gLogFilter, Debug::Verbose, "<--Controller::GetNaturalSize cached %f,%f,%f\n", naturalSizeVec3.x, naturalSizeVec3.y, naturalSizeVec3.z);
192   }
193
194   naturalSizeVec3.x = ConvertToEven(naturalSizeVec3.x);
195   naturalSizeVec3.y = ConvertToEven(naturalSizeVec3.y);
196
197   return naturalSizeVec3;
198 }
199
200 bool Controller::Relayouter::CheckForTextFit(Controller& controller, float pointSize, const Size& layoutSize)
201 {
202   Size              textSize;
203   Controller::Impl& impl            = *controller.mImpl;
204   TextUpdateInfo&   textUpdateInfo  = impl.mTextUpdateInfo;
205   impl.mFontDefaults->mFitPointSize = pointSize;
206   impl.mFontDefaults->sizeDefined   = true;
207   impl.ClearFontData();
208
209   // Operations that can be done only once until the text changes.
210   const OperationsMask onlyOnceOperations = static_cast<OperationsMask>(CONVERT_TO_UTF32 |
211                                                                         GET_SCRIPTS |
212                                                                         VALIDATE_FONTS |
213                                                                         GET_LINE_BREAKS |
214                                                                         BIDI_INFO |
215                                                                         SHAPE_TEXT |
216                                                                         GET_GLYPH_METRICS);
217
218   textUpdateInfo.mParagraphCharacterIndex     = 0u;
219   textUpdateInfo.mRequestedNumberOfCharacters = impl.mModel->mLogicalModel->mText.Count();
220
221   // Make sure the model is up-to-date before layouting
222   impl.UpdateModel(onlyOnceOperations);
223
224   DoRelayout(impl,
225              Size(layoutSize.width, MAX_FLOAT),
226              static_cast<OperationsMask>(onlyOnceOperations | LAYOUT),
227              textSize);
228
229   // Clear the update info. This info will be set the next time the text is updated.
230   textUpdateInfo.Clear();
231   textUpdateInfo.mClearAll = true;
232
233   if(textSize.width > layoutSize.width || textSize.height > layoutSize.height)
234   {
235     return false;
236   }
237   return true;
238 }
239
240 void Controller::Relayouter::FitPointSizeforLayout(Controller& controller, const Size& layoutSize)
241 {
242   Controller::Impl& impl = *controller.mImpl;
243
244   const OperationsMask operations = impl.mOperationsPending;
245   if(NO_OPERATION != (UPDATE_LAYOUT_SIZE & operations) || impl.mTextFitContentSize != layoutSize)
246   {
247     ModelPtr& model = impl.mModel;
248
249     bool  actualellipsis      = model->mElideEnabled;
250     float minPointSize        = impl.mTextFitMinSize;
251     float maxPointSize        = impl.mTextFitMaxSize;
252     float pointInterval       = impl.mTextFitStepSize;
253     float currentFitPointSize = impl.mFontDefaults->mFitPointSize;
254
255     model->mElideEnabled = false;
256     Vector<float> pointSizeArray;
257
258     // check zero value
259     if(pointInterval < 1.f)
260     {
261       impl.mTextFitStepSize = pointInterval = 1.0f;
262     }
263
264     pointSizeArray.Reserve(static_cast<unsigned int>(ceil((maxPointSize - minPointSize) / pointInterval)));
265
266     for(float i = minPointSize; i < maxPointSize; i += pointInterval)
267     {
268       pointSizeArray.PushBack(i);
269     }
270
271     pointSizeArray.PushBack(maxPointSize);
272
273     int bestSizeIndex = 0;
274     int min           = bestSizeIndex + 1;
275     int max           = pointSizeArray.Size() - 1;
276     while(min <= max)
277     {
278       int destI = (min + max) / 2;
279
280       if(CheckForTextFit(controller, pointSizeArray[destI], layoutSize))
281       {
282         bestSizeIndex = min;
283         min           = destI + 1;
284       }
285       else
286       {
287         max           = destI - 1;
288         bestSizeIndex = max;
289       }
290     }
291
292     model->mElideEnabled = actualellipsis;
293     if(currentFitPointSize != pointSizeArray[bestSizeIndex])
294     {
295       impl.mTextFitChanged = true;
296     }
297     impl.mFontDefaults->mFitPointSize = pointSizeArray[bestSizeIndex];
298     impl.mFontDefaults->sizeDefined   = true;
299     impl.ClearFontData();
300   }
301 }
302
303 float Controller::Relayouter::GetHeightForWidth(Controller& controller, float width)
304 {
305   DALI_LOG_INFO(gLogFilter, Debug::Verbose, "-->Controller::GetHeightForWidth %p width %f\n", &controller, width);
306
307   // Make sure the model is up-to-date before layouting
308   EventHandler::ProcessModifyEvents(controller);
309
310   Controller::Impl& impl           = *controller.mImpl;
311   ModelPtr&         model          = impl.mModel;
312   VisualModelPtr&   visualModel    = model->mVisualModel;
313   TextUpdateInfo&   textUpdateInfo = impl.mTextUpdateInfo;
314
315   Size layoutSize;
316
317   if(fabsf(width - visualModel->mControlSize.width) > Math::MACHINE_EPSILON_1000 ||
318      textUpdateInfo.mFullRelayoutNeeded ||
319      textUpdateInfo.mClearAll)
320   {
321     // Layout the text for the new width.
322     OperationsMask requestedOperationsMask        = static_cast<OperationsMask>(LAYOUT);
323     Size           sizeRequestedWidthAndMaxHeight = Size(width, MAX_FLOAT);
324
325     layoutSize = CalculateLayoutSizeOnRequiredControllerSize(controller, sizeRequestedWidthAndMaxHeight, requestedOperationsMask);
326
327     DALI_LOG_INFO(gLogFilter, Debug::Verbose, "<--Controller::GetHeightForWidth calculated %f\n", layoutSize.height);
328   }
329   else
330   {
331     layoutSize = visualModel->GetLayoutSize();
332     DALI_LOG_INFO(gLogFilter, Debug::Verbose, "<--Controller::GetHeightForWidth cached %f\n", layoutSize.height);
333   }
334
335   return layoutSize.height;
336 }
337
338 Controller::UpdateTextType Controller::Relayouter::Relayout(Controller& controller, const Size& size, Dali::LayoutDirection::Type layoutDirection)
339 {
340   Controller::Impl& impl           = *controller.mImpl;
341   ModelPtr&         model          = impl.mModel;
342   VisualModelPtr&   visualModel    = model->mVisualModel;
343   TextUpdateInfo&   textUpdateInfo = impl.mTextUpdateInfo;
344
345   DALI_LOG_INFO(gLogFilter, Debug::Verbose, "-->Controller::Relayout %p size %f,%f, autoScroll[%s]\n", &controller, size.width, size.height, impl.mIsAutoScrollEnabled ? "true" : "false");
346
347   UpdateTextType updateTextType = NONE_UPDATED;
348
349   if((size.width < Math::MACHINE_EPSILON_1000) || (size.height < Math::MACHINE_EPSILON_1000))
350   {
351     if(0u != visualModel->mGlyphPositions.Count())
352     {
353       visualModel->mGlyphPositions.Clear();
354       updateTextType = MODEL_UPDATED;
355     }
356
357     // Clear the update info. This info will be set the next time the text is updated.
358     textUpdateInfo.Clear();
359
360     // Not worth to relayout if width or height is equal to zero.
361     DALI_LOG_INFO(gLogFilter, Debug::Verbose, "<--Controller::Relayout (skipped)\n");
362
363     return updateTextType;
364   }
365
366   // Whether a new size has been set.
367   const bool newSize = (size != visualModel->mControlSize);
368
369   // Get a reference to the pending operations member
370   OperationsMask& operationsPending = impl.mOperationsPending;
371
372   if(newSize)
373   {
374     DALI_LOG_INFO(gLogFilter, Debug::Verbose, "new size (previous size %f,%f)\n", visualModel->mControlSize.width, visualModel->mControlSize.height);
375
376     if((0 == textUpdateInfo.mNumberOfCharactersToAdd) &&
377        (0 == textUpdateInfo.mPreviousNumberOfCharacters) &&
378        ((visualModel->mControlSize.width < Math::MACHINE_EPSILON_1000) || (visualModel->mControlSize.height < Math::MACHINE_EPSILON_1000)))
379     {
380       textUpdateInfo.mNumberOfCharactersToAdd = model->mLogicalModel->mText.Count();
381     }
382
383     // Layout operations that need to be done if the size changes.
384     operationsPending = static_cast<OperationsMask>(operationsPending |
385                                                     LAYOUT |
386                                                     ALIGN |
387                                                     UPDATE_LAYOUT_SIZE |
388                                                     REORDER);
389     // Set the update info to relayout the whole text.
390     textUpdateInfo.mFullRelayoutNeeded = true;
391     textUpdateInfo.mCharacterIndex     = 0u;
392
393     // Store the size used to layout the text.
394     visualModel->mControlSize = size;
395   }
396
397   // Whether there are modify events.
398   if(0u != impl.mModifyEvents.Count())
399   {
400     // Style operations that need to be done if the text is modified.
401     operationsPending = static_cast<OperationsMask>(operationsPending | COLOR);
402   }
403
404   // Set the update info to elide the text.
405   if(model->mElideEnabled ||
406      ((NULL != impl.mEventData) && impl.mEventData->mIsPlaceholderElideEnabled))
407   {
408     // Update Text layout for applying elided
409     operationsPending                  = static_cast<OperationsMask>(operationsPending |
410                                                     ALIGN |
411                                                     LAYOUT |
412                                                     UPDATE_LAYOUT_SIZE |
413                                                     REORDER);
414     textUpdateInfo.mFullRelayoutNeeded = true;
415     textUpdateInfo.mCharacterIndex     = 0u;
416   }
417
418   bool layoutDirectionChanged = false;
419   if(impl.mLayoutDirection != layoutDirection)
420   {
421     // Flag to indicate that the layout direction has changed.
422     layoutDirectionChanged = true;
423     // Clear the update info. This info will be set the next time the text is updated.
424     textUpdateInfo.mClearAll = true;
425     // Apply modifications to the model
426     // Shape the text again is needed because characters like '()[]{}' have to be mirrored and the glyphs generated again.
427     operationsPending     = static_cast<OperationsMask>(operationsPending |
428                                                     GET_GLYPH_METRICS |
429                                                     SHAPE_TEXT |
430                                                     UPDATE_DIRECTION |
431                                                     ALIGN |
432                                                     LAYOUT |
433                                                     BIDI_INFO |
434                                                     REORDER);
435     impl.mLayoutDirection = layoutDirection;
436   }
437
438   // Make sure the model is up-to-date before layouting.
439   EventHandler::ProcessModifyEvents(controller);
440   bool updated = impl.UpdateModel(operationsPending);
441
442   // Layout the text.
443   Size layoutSize;
444   updated = DoRelayout(impl, size, operationsPending, layoutSize) || updated;
445
446   if(updated)
447   {
448     updateTextType = MODEL_UPDATED;
449   }
450
451   // Do not re-do any operation until something changes.
452   operationsPending          = NO_OPERATION;
453   model->mScrollPositionLast = model->mScrollPosition;
454
455   // Whether the text control is editable
456   const bool isEditable = NULL != impl.mEventData;
457
458   // Keep the current offset as it will be used to update the decorator's positions (if the size changes).
459   Vector2 offset;
460   if(newSize && isEditable)
461   {
462     offset = model->mScrollPosition;
463   }
464
465   if(!isEditable || !controller.IsMultiLineEnabled())
466   {
467     // After doing the text layout, the vertical offset to place the actor in the desired position can be calculated.
468     CalculateVerticalOffset(impl, size);
469   }
470   else // TextEditor
471   {
472     // If layoutSize is bigger than size, vertical align has no meaning.
473     if(layoutSize.y < size.y)
474     {
475       CalculateVerticalOffset(impl, size);
476       if(impl.mEventData)
477       {
478         impl.mEventData->mScrollAfterDelete = false;
479       }
480     }
481   }
482
483   if(isEditable)
484   {
485     if(newSize || layoutDirectionChanged)
486     {
487       // If there is a new size or layout direction is changed, the scroll position needs to be clamped.
488       impl.ClampHorizontalScroll(layoutSize);
489
490       // Update the decorator's positions is needed if there is a new size.
491       impl.mEventData->mDecorator->UpdatePositions(model->mScrollPosition - offset);
492
493       // All decorator elements need to be updated.
494       if(EventData::IsEditingState(impl.mEventData->mState))
495       {
496         impl.mEventData->mScrollAfterUpdatePosition = true;
497         impl.mEventData->mUpdateCursorPosition      = true;
498         impl.mEventData->mUpdateGrabHandlePosition  = true;
499       }
500       else if(impl.mEventData->mState == EventData::SELECTING)
501       {
502         impl.mEventData->mUpdateHighlightBox = true;
503       }
504     }
505
506     // Move the cursor, grab handle etc.
507     if(impl.ProcessInputEvents())
508     {
509       updateTextType = static_cast<UpdateTextType>(updateTextType | DECORATOR_UPDATED);
510     }
511   }
512
513   // Clear the update info. This info will be set the next time the text is updated.
514   textUpdateInfo.Clear();
515   DALI_LOG_INFO(gLogFilter, Debug::Verbose, "<--Controller::Relayout\n");
516
517   return updateTextType;
518 }
519
520 bool Controller::Relayouter::DoRelayout(Controller::Impl& impl, const Size& size, OperationsMask operationsRequired, Size& layoutSize)
521 {
522   DALI_LOG_INFO(gLogFilter, Debug::Verbose, "-->Controller::Relayouter::DoRelayout %p size %f,%f\n", &impl, size.width, size.height);
523   bool viewUpdated(false);
524
525   // Calculate the operations to be done.
526   const OperationsMask operations = static_cast<OperationsMask>(impl.mOperationsPending & operationsRequired);
527
528   TextUpdateInfo&      textUpdateInfo              = impl.mTextUpdateInfo;
529   const CharacterIndex startIndex                  = textUpdateInfo.mParagraphCharacterIndex;
530   const Length         requestedNumberOfCharacters = textUpdateInfo.mRequestedNumberOfCharacters;
531
532   // Get the current layout size.
533   VisualModelPtr& visualModel = impl.mModel->mVisualModel;
534   layoutSize                  = visualModel->GetLayoutSize();
535
536   if(NO_OPERATION != (LAYOUT & operations))
537   {
538     DALI_LOG_INFO(gLogFilter, Debug::Verbose, "-->Controller::DoRelayout LAYOUT & operations\n");
539
540     // Some vectors with data needed to layout and reorder may be void
541     // after the first time the text has been laid out.
542     // Fill the vectors again.
543
544     // Calculate the number of glyphs to layout.
545     const Vector<GlyphIndex>& charactersToGlyph        = visualModel->mCharactersToGlyph;
546     const Vector<Length>&     glyphsPerCharacter       = visualModel->mGlyphsPerCharacter;
547     const GlyphIndex* const   charactersToGlyphBuffer  = charactersToGlyph.Begin();
548     const Length* const       glyphsPerCharacterBuffer = glyphsPerCharacter.Begin();
549
550     const CharacterIndex lastIndex       = startIndex + ((requestedNumberOfCharacters > 0u) ? requestedNumberOfCharacters - 1u : 0u);
551     const GlyphIndex     startGlyphIndex = textUpdateInfo.mStartGlyphIndex;
552
553     // Make sure the index is not out of bound
554     if(charactersToGlyph.Count() != glyphsPerCharacter.Count() ||
555        requestedNumberOfCharacters > charactersToGlyph.Count() ||
556        (lastIndex > charactersToGlyph.Count() && charactersToGlyph.Count() > 0u))
557     {
558       std::string currentText;
559       impl.GetText(currentText);
560
561       DALI_LOG_ERROR("Controller::DoRelayout: Attempting to access invalid buffer\n");
562       DALI_LOG_ERROR("Current text is: %s\n", currentText.c_str());
563       DALI_LOG_ERROR("startIndex: %u, lastIndex: %u, requestedNumberOfCharacters: %u, charactersToGlyph.Count = %lu, glyphsPerCharacter.Count = %lu\n", startIndex, lastIndex, requestedNumberOfCharacters, charactersToGlyph.Count(), glyphsPerCharacter.Count());
564
565       return false;
566     }
567
568     const Length numberOfGlyphs      = (requestedNumberOfCharacters > 0u) ? *(charactersToGlyphBuffer + lastIndex) + *(glyphsPerCharacterBuffer + lastIndex) - startGlyphIndex : 0u;
569     const Length totalNumberOfGlyphs = visualModel->mGlyphs.Count();
570
571     if(0u == totalNumberOfGlyphs)
572     {
573       if(NO_OPERATION != (UPDATE_LAYOUT_SIZE & operations))
574       {
575         visualModel->SetLayoutSize(Size::ZERO);
576       }
577
578       // Nothing else to do if there is no glyphs.
579       DALI_LOG_INFO(gLogFilter, Debug::Verbose, "<--Controller::DoRelayout no glyphs, view updated true\n");
580       return true;
581     }
582
583     // Set the layout parameters.
584     Layout::Parameters layoutParameters(size, impl.mModel);
585
586     // Resize the vector of positions to have the same size than the vector of glyphs.
587     Vector<Vector2>& glyphPositions = visualModel->mGlyphPositions;
588     glyphPositions.Resize(totalNumberOfGlyphs);
589
590     // Whether the last character is a new paragraph character.
591     const Character* const textBuffer           = impl.mModel->mLogicalModel->mText.Begin();
592     textUpdateInfo.mIsLastCharacterNewParagraph = TextAbstraction::IsNewParagraph(*(textBuffer + (impl.mModel->mLogicalModel->mText.Count() - 1u)));
593     layoutParameters.isLastNewParagraph         = textUpdateInfo.mIsLastCharacterNewParagraph;
594
595     // The initial glyph and the number of glyphs to layout.
596     layoutParameters.startGlyphIndex        = startGlyphIndex;
597     layoutParameters.numberOfGlyphs         = numberOfGlyphs;
598     layoutParameters.startLineIndex         = textUpdateInfo.mStartLineIndex;
599     layoutParameters.estimatedNumberOfLines = textUpdateInfo.mEstimatedNumberOfLines;
600
601     // Update the ellipsis
602     bool elideTextEnabled = impl.mModel->mElideEnabled;
603     auto ellipsisPosition = impl.mModel->mEllipsisPosition;
604
605     if(NULL != impl.mEventData)
606     {
607       if(impl.mEventData->mPlaceholderEllipsisFlag && impl.IsShowingPlaceholderText())
608       {
609         elideTextEnabled = impl.mEventData->mIsPlaceholderElideEnabled;
610       }
611       else if(EventData::INACTIVE != impl.mEventData->mState)
612       {
613         // Disable ellipsis when editing
614         elideTextEnabled = false;
615       }
616
617       // Reset the scroll position in inactive state
618       if(elideTextEnabled && (impl.mEventData->mState == EventData::INACTIVE))
619       {
620         impl.ResetScrollPosition();
621       }
622     }
623
624     // Update the visual model.
625     bool isAutoScrollEnabled            = impl.mIsAutoScrollEnabled;
626     bool isAutoScrollMaxTextureExceeded = impl.mIsAutoScrollMaxTextureExceeded;
627
628     Size newLayoutSize;
629     viewUpdated               = impl.mLayoutEngine.LayoutText(layoutParameters,
630                                                 newLayoutSize,
631                                                 elideTextEnabled,
632                                                 isAutoScrollEnabled,
633                                                 isAutoScrollMaxTextureExceeded,
634                                                 ellipsisPosition);
635     impl.mIsAutoScrollEnabled = isAutoScrollEnabled;
636
637     viewUpdated = viewUpdated || (newLayoutSize != layoutSize);
638
639     if(viewUpdated)
640     {
641       layoutSize = newLayoutSize;
642
643       if(NO_OPERATION != (UPDATE_DIRECTION & operations))
644       {
645         impl.mIsTextDirectionRTL = false;
646       }
647
648       if((NO_OPERATION != (UPDATE_DIRECTION & operations)) && !visualModel->mLines.Empty())
649       {
650         impl.mIsTextDirectionRTL = visualModel->mLines[0u].direction;
651       }
652
653       // Sets the layout size.
654       if(NO_OPERATION != (UPDATE_LAYOUT_SIZE & operations))
655       {
656         visualModel->SetLayoutSize(layoutSize);
657       }
658     } // view updated
659   }
660
661   if(NO_OPERATION != (ALIGN & operations))
662   {
663     DoRelayoutHorizontalAlignment(impl, size, startIndex, requestedNumberOfCharacters);
664     viewUpdated = true;
665   }
666 #if defined(DEBUG_ENABLED)
667   std::string currentText;
668   impl.GetText(currentText);
669   DALI_LOG_INFO(gLogFilter, Debug::Concise, "Controller::Relayouter::DoRelayout [%p] mImpl->mIsTextDirectionRTL[%s] [%s]\n", &impl, (impl.mIsTextDirectionRTL) ? "true" : "false", currentText.c_str());
670 #endif
671   DALI_LOG_INFO(gLogFilter, Debug::Verbose, "<--Controller::Relayouter::DoRelayout, view updated %s\n", (viewUpdated ? "true" : "false"));
672   return viewUpdated;
673 }
674
675 void Controller::Relayouter::DoRelayoutHorizontalAlignment(Controller::Impl&    impl,
676                                                            const Size&          size,
677                                                            const CharacterIndex startIndex,
678                                                            const Length         requestedNumberOfCharacters)
679 {
680   // The visualModel
681   VisualModelPtr& visualModel = impl.mModel->mVisualModel;
682
683   // The laid-out lines.
684   Vector<LineRun>& lines = visualModel->mLines;
685
686   CharacterIndex alignStartIndex                  = startIndex;
687   Length         alignRequestedNumberOfCharacters = requestedNumberOfCharacters;
688
689   // the whole text needs to be full aligned.
690   // If you do not do a full aligned, only the last line of the multiline input is aligned.
691   if(impl.mEventData && impl.mEventData->mUpdateAlignment)
692   {
693     alignStartIndex                   = 0u;
694     alignRequestedNumberOfCharacters  = impl.mModel->mLogicalModel->mText.Count();
695     impl.mEventData->mUpdateAlignment = false;
696   }
697
698   // If there is no BoundedParagraphRuns then apply the alignment of controller.
699   // Check whether the layout is single line. It's needed to apply one alignment for single-line.
700   // In single-line layout case we need to check whether to follow the alignment of controller or the first BoundedParagraph.
701   // Apply BoundedParagraph's alignment if and only if there is one BoundedParagraph contains all characters. Otherwise follow controller's alignment.
702   const bool isFollowControllerAlignment = ((impl.mModel->GetNumberOfBoundedParagraphRuns() == 0u) ||
703                                             ((Layout::Engine::SINGLE_LINE_BOX == impl.mLayoutEngine.GetLayout()) &&
704                                              (impl.mModel->GetBoundedParagraphRuns()[0].characterRun.numberOfCharacters != impl.mModel->mLogicalModel->mText.Count())));
705
706   if(isFollowControllerAlignment)
707   {
708     // Need to align with the control's size as the text may contain lines
709     // starting either with left to right text or right to left.
710     impl.mLayoutEngine.Align(size,
711                              alignStartIndex,
712                              alignRequestedNumberOfCharacters,
713                              impl.mModel->mHorizontalAlignment,
714                              lines,
715                              impl.mModel->mAlignmentOffset,
716                              impl.mLayoutDirection,
717                              (impl.mModel->mMatchLayoutDirection != DevelText::MatchLayoutDirection::CONTENTS));
718   }
719   else
720   {
721     //Override the controller horizontal-alignment by horizontal-alignment of bounded paragraph.
722     const Length&                      numberOfBoundedParagraphRuns = impl.mModel->GetNumberOfBoundedParagraphRuns();
723     const Vector<BoundedParagraphRun>& boundedParagraphRuns         = impl.mModel->GetBoundedParagraphRuns();
724     const CharacterIndex               alignEndIndex                = alignStartIndex + alignRequestedNumberOfCharacters - 1u;
725
726     Length alignIndex               = alignStartIndex;
727     Length boundedParagraphRunIndex = 0u;
728
729     while(alignIndex <= alignEndIndex && boundedParagraphRunIndex < numberOfBoundedParagraphRuns)
730     {
731       //BP: BoundedParagraph
732       const BoundedParagraphRun& boundedParagraphRun   = boundedParagraphRuns[boundedParagraphRunIndex];
733       const CharacterIndex&      characterStartIndexBP = boundedParagraphRun.characterRun.characterIndex;
734       const Length&              numberOfCharactersBP  = boundedParagraphRun.characterRun.numberOfCharacters;
735       const CharacterIndex       characterEndIndexBP   = characterStartIndexBP + numberOfCharactersBP - 1u;
736
737       CharacterIndex                  decidedAlignStartIndex         = alignIndex;
738       Length                          decidedAlignNumberOfCharacters = alignEndIndex - alignIndex + 1u;
739       Text::HorizontalAlignment::Type decidedHorizontalAlignment     = impl.mModel->mHorizontalAlignment;
740
741       /*
742          * Shortcuts to explain indexes cases:
743          *
744          * AS: Alignment Start Index
745          * AE: Alignment End Index
746          * PS: Paragraph Start Index
747          * PE: Paragraph End Index
748          * B: BoundedParagraph Alignment
749          * M: Model Alignment
750          *
751          */
752
753       if(alignIndex < characterStartIndexBP && characterStartIndexBP <= alignEndIndex) /// AS.MMMMMM.PS--------AE
754       {
755         // Alignment from "Alignment Start Index" to index before "Paragraph Start Index" according to "Model Alignment"
756         decidedAlignStartIndex         = alignIndex;
757         decidedAlignNumberOfCharacters = characterStartIndexBP - alignIndex;
758         decidedHorizontalAlignment     = impl.mModel->mHorizontalAlignment;
759
760         // Need to re-heck the case of current bounded paragraph
761         alignIndex = characterStartIndexBP; // Shift AS to be PS
762       }
763       else if((characterStartIndexBP <= alignIndex && alignIndex <= characterEndIndexBP) ||     /// ---PS.BBBBBBB.AS.BBBBBBB.PE---
764               (characterStartIndexBP <= alignEndIndex && alignEndIndex <= characterEndIndexBP)) /// ---PS.BBBBBB.AE.BBBBBBB.PE---
765       {
766         // Alignment from "Paragraph Start Index" to "Paragraph End Index" according to "BoundedParagraph Alignment"
767         decidedAlignStartIndex         = characterStartIndexBP;
768         decidedAlignNumberOfCharacters = numberOfCharactersBP;
769         decidedHorizontalAlignment     = boundedParagraphRun.horizontalAlignmentDefined ? boundedParagraphRun.horizontalAlignment : impl.mModel->mHorizontalAlignment;
770
771         alignIndex = characterEndIndexBP + 1u; // Shift AS to be after PE direct
772         boundedParagraphRunIndex++;            // Align then check the case of next bounded paragraph
773       }
774       else
775       {
776         boundedParagraphRunIndex++; // Check the case of next bounded paragraph
777         continue;
778       }
779
780       impl.mLayoutEngine.Align(size,
781                                decidedAlignStartIndex,
782                                decidedAlignNumberOfCharacters,
783                                decidedHorizontalAlignment,
784                                lines,
785                                impl.mModel->mAlignmentOffset,
786                                impl.mLayoutDirection,
787                                (impl.mModel->mMatchLayoutDirection != DevelText::MatchLayoutDirection::CONTENTS));
788     }
789
790     //Align the remaining that is not aligned
791     if(alignIndex <= alignEndIndex)
792     {
793       impl.mLayoutEngine.Align(size,
794                                alignIndex,
795                                (alignEndIndex - alignIndex + 1u),
796                                impl.mModel->mHorizontalAlignment,
797                                lines,
798                                impl.mModel->mAlignmentOffset,
799                                impl.mLayoutDirection,
800                                (impl.mModel->mMatchLayoutDirection != DevelText::MatchLayoutDirection::CONTENTS));
801     }
802   }
803 }
804
805 void Controller::Relayouter::CalculateVerticalOffset(Controller::Impl& impl, const Size& controlSize)
806 {
807   ModelPtr&       model                 = impl.mModel;
808   VisualModelPtr& visualModel           = model->mVisualModel;
809   Size            layoutSize            = model->mVisualModel->GetLayoutSize();
810   Size            oldLayoutSize         = layoutSize;
811   float           offsetY               = 0.f;
812   bool            needRecalc            = false;
813   float           defaultFontLineHeight = impl.GetDefaultFontLineHeight();
814
815   if(fabsf(layoutSize.height) < Math::MACHINE_EPSILON_1000)
816   {
817     // Get the line height of the default font.
818     layoutSize.height = defaultFontLineHeight;
819   }
820
821   // Whether the text control is editable
822   const bool isEditable = NULL != impl.mEventData;
823   if(isEditable && layoutSize.height != defaultFontLineHeight && impl.IsShowingPlaceholderText())
824   {
825     // This code prevents the wrong positioning of cursor when the layout size is bigger/smaller than defaultFontLineHeight.
826     // This situation occurs when the size of placeholder text is different from the default text.
827     layoutSize.height = defaultFontLineHeight;
828     needRecalc        = true;
829   }
830
831   switch(model->mVerticalAlignment)
832   {
833     case VerticalAlignment::TOP:
834     {
835       model->mScrollPosition.y = 0.f;
836       offsetY                  = 0.f;
837       break;
838     }
839     case VerticalAlignment::CENTER:
840     {
841       model->mScrollPosition.y = floorf(0.5f * (controlSize.height - layoutSize.height)); // try to avoid pixel alignment.
842       if(needRecalc) offsetY = floorf(0.5f * (layoutSize.height - oldLayoutSize.height));
843       break;
844     }
845     case VerticalAlignment::BOTTOM:
846     {
847       model->mScrollPosition.y = controlSize.height - layoutSize.height;
848       if(needRecalc) offsetY = layoutSize.height - oldLayoutSize.height;
849       break;
850     }
851   }
852
853   if(needRecalc)
854   {
855     // Update glyphPositions according to recalculation.
856     const Length     positionCount  = visualModel->mGlyphPositions.Count();
857     Vector<Vector2>& glyphPositions = visualModel->mGlyphPositions;
858     for(Length index = 0u; index < positionCount; index++)
859     {
860       glyphPositions[index].y += offsetY;
861     }
862   }
863 }
864
865 } // namespace Text
866
867 } // namespace Toolkit
868
869 } // namespace Dali