Merge "Forced all ControlWrapper instances to ignore styling" into devel/master
[platform/core/uifw/dali-toolkit.git] / dali-toolkit / internal / text / rendering / view-model.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/rendering/view-model.h>
20
21 // EXTERNAL INCLUDES
22 #include <dali/devel-api/text-abstraction/font-client.h>
23 #include <memory.h>
24
25 // INTERNAL INCLUDES
26 #include <dali-toolkit/internal/text/line-run.h>
27
28 namespace Dali
29 {
30 namespace Toolkit
31 {
32 namespace Text
33 {
34 ViewModel::ViewModel(const ModelInterface* const model)
35 : mModel(model),
36   mElidedGlyphs(),
37   mElidedLayout(),
38   mIsTextElided(false),
39   mStartIndexOfElidedGlyphs(0u),
40   mEndIndexOfElidedGlyphs(0u),
41   mFirstMiddleIndexOfElidedGlyphs(0u),
42   mSecondMiddleIndexOfElidedGlyphs(0u)
43 {
44 }
45
46 ViewModel::~ViewModel()
47 {
48 }
49
50 const Size& ViewModel::GetControlSize() const
51 {
52   return mModel->GetControlSize();
53 }
54
55 const Size& ViewModel::GetLayoutSize() const
56 {
57   return mModel->GetLayoutSize();
58 }
59
60 const Vector2& ViewModel::GetScrollPosition() const
61 {
62   return mModel->GetScrollPosition();
63 }
64
65 HorizontalAlignment::Type ViewModel::GetHorizontalAlignment() const
66 {
67   return mModel->GetHorizontalAlignment();
68 }
69
70 VerticalAlignment::Type ViewModel::GetVerticalAlignment() const
71 {
72   return mModel->GetVerticalAlignment();
73 }
74
75 DevelText::VerticalLineAlignment::Type ViewModel::GetVerticalLineAlignment() const
76 {
77   return mModel->GetVerticalLineAlignment();
78 }
79
80 DevelText::EllipsisPosition::Type ViewModel::GetEllipsisPosition() const
81 {
82   return mModel->GetEllipsisPosition();
83 }
84
85 bool ViewModel::IsTextElideEnabled() const
86 {
87   return mModel->IsTextElideEnabled();
88 }
89
90 Length ViewModel::GetNumberOfLines() const
91 {
92   return mModel->GetNumberOfLines();
93 }
94
95 const LineRun* const ViewModel::GetLines() const
96 {
97   return mModel->GetLines();
98 }
99
100 Length ViewModel::GetNumberOfScripts() const
101 {
102   return mModel->GetNumberOfScripts();
103 }
104
105 const ScriptRun* const ViewModel::GetScriptRuns() const
106 {
107   return mModel->GetScriptRuns();
108 }
109
110 Length ViewModel::GetNumberOfGlyphs() const
111 {
112   if(mIsTextElided && mModel->IsTextElideEnabled())
113   {
114     return mElidedGlyphs.Count();
115   }
116   else
117   {
118     return mModel->GetNumberOfGlyphs();
119   }
120
121   return 0u;
122 }
123
124 GlyphIndex ViewModel::GetStartIndexOfElidedGlyphs() const
125 {
126   if(mIsTextElided && mModel->IsTextElideEnabled())
127   {
128     return mStartIndexOfElidedGlyphs;
129   }
130
131   return mModel->GetStartIndexOfElidedGlyphs();
132 }
133
134 GlyphIndex ViewModel::GetEndIndexOfElidedGlyphs() const
135 {
136   if(mIsTextElided && mModel->IsTextElideEnabled())
137   {
138     return mEndIndexOfElidedGlyphs;
139   }
140
141   return mModel->GetEndIndexOfElidedGlyphs();
142 }
143
144 GlyphIndex ViewModel::GetFirstMiddleIndexOfElidedGlyphs() const
145 {
146   if(mIsTextElided && mModel->IsTextElideEnabled())
147   {
148     return mFirstMiddleIndexOfElidedGlyphs;
149   }
150
151   return mModel->GetFirstMiddleIndexOfElidedGlyphs();
152 }
153
154 GlyphIndex ViewModel::GetSecondMiddleIndexOfElidedGlyphs() const
155 {
156   if(mIsTextElided && mModel->IsTextElideEnabled())
157   {
158     return mSecondMiddleIndexOfElidedGlyphs;
159   }
160
161   return mModel->GetSecondMiddleIndexOfElidedGlyphs();
162 }
163
164 const GlyphInfo* const ViewModel::GetGlyphs() const
165 {
166   if(mIsTextElided && mModel->IsTextElideEnabled())
167   {
168     return mElidedGlyphs.Begin();
169   }
170   else
171   {
172     return mModel->GetGlyphs();
173   }
174
175   return NULL;
176 }
177
178 const Vector2* const ViewModel::GetLayout() const
179 {
180   if(mIsTextElided && mModel->IsTextElideEnabled())
181   {
182     return mElidedLayout.Begin();
183   }
184   else
185   {
186     return mModel->GetLayout();
187   }
188
189   return NULL;
190 }
191
192 const Vector4* const ViewModel::GetColors() const
193 {
194   return mModel->GetColors();
195 }
196
197 const ColorIndex* const ViewModel::GetColorIndices() const
198 {
199   return mModel->GetColorIndices();
200 }
201
202 const Vector4* const ViewModel::GetBackgroundColors() const
203 {
204   return mModel->GetBackgroundColors();
205 }
206
207 const ColorIndex* const ViewModel::GetBackgroundColorIndices() const
208 {
209   return mModel->GetBackgroundColorIndices();
210 }
211
212 bool const ViewModel::IsMarkupBackgroundColorSet() const
213 {
214   return mModel->IsMarkupBackgroundColorSet();
215 }
216
217 const Vector4& ViewModel::GetDefaultColor() const
218 {
219   return mModel->GetDefaultColor();
220 }
221
222 const Vector2& ViewModel::GetShadowOffset() const
223 {
224   return mModel->GetShadowOffset();
225 }
226
227 const Vector4& ViewModel::GetShadowColor() const
228 {
229   return mModel->GetShadowColor();
230 }
231
232 const float& ViewModel::GetShadowBlurRadius() const
233 {
234   return mModel->GetShadowBlurRadius();
235 }
236
237 const Vector4& ViewModel::GetUnderlineColor() const
238 {
239   return mModel->GetUnderlineColor();
240 }
241
242 bool ViewModel::IsUnderlineEnabled() const
243 {
244   return mModel->IsUnderlineEnabled();
245 }
246
247 float ViewModel::GetUnderlineHeight() const
248 {
249   return mModel->GetUnderlineHeight();
250 }
251
252 Text::Underline::Type ViewModel::GetUnderlineType() const
253 {
254   return mModel->GetUnderlineType();
255 }
256
257 float ViewModel::GetDashedUnderlineWidth() const
258 {
259   return mModel->GetDashedUnderlineWidth();
260 }
261
262 float ViewModel::GetDashedUnderlineGap() const
263 {
264   return mModel->GetDashedUnderlineGap();
265 }
266
267 Length ViewModel::GetNumberOfUnderlineRuns() const
268 {
269   return mModel->GetNumberOfUnderlineRuns();
270 }
271
272 void ViewModel::GetUnderlineRuns(GlyphRun* underlineRuns, UnderlineRunIndex index, Length numberOfRuns) const
273 {
274   mModel->GetUnderlineRuns(underlineRuns, index, numberOfRuns);
275 }
276
277 const Vector4& ViewModel::GetOutlineColor() const
278 {
279   return mModel->GetOutlineColor();
280 }
281
282 uint16_t ViewModel::GetOutlineWidth() const
283 {
284   return mModel->GetOutlineWidth();
285 }
286
287 const Vector4& ViewModel::GetBackgroundColor() const
288 {
289   return mModel->GetBackgroundColor();
290 }
291
292 bool ViewModel::IsBackgroundEnabled() const
293 {
294   return mModel->IsBackgroundEnabled();
295 }
296
297 bool ViewModel::IsMarkupProcessorEnabled() const
298 {
299   return mModel->IsMarkupProcessorEnabled();
300 }
301
302 const GlyphInfo* ViewModel::GetHyphens() const
303 {
304   return mModel->GetHyphens();
305 }
306
307 const Length* ViewModel::GetHyphenIndices() const
308 {
309   return mModel->GetHyphenIndices();
310 }
311
312 Length ViewModel::GetHyphensCount() const
313 {
314   return mModel->GetHyphensCount();
315 }
316
317 void ViewModel::ElideGlyphs()
318 {
319   mIsTextElided             = false;
320   mStartIndexOfElidedGlyphs = mFirstMiddleIndexOfElidedGlyphs = mSecondMiddleIndexOfElidedGlyphs = 0;
321   mEndIndexOfElidedGlyphs                                                                        = mModel->GetNumberOfGlyphs() - 1u;
322
323   auto ellipsisPosition = GetEllipsisPosition();
324
325   if(IsTextElideEnabled())
326   {
327     const Length numberOfLines = mModel->GetNumberOfLines();
328     if(0u != numberOfLines)
329     {
330       const LineRun* const lines = mModel->GetLines();
331
332       //Get line of ellipsis
333       const LineRun* ellipsisLine     = nullptr;
334       const LineRun* ellipsisNextLine = nullptr;
335
336       for(Length lineIndex = 0; lineIndex < numberOfLines; lineIndex++)
337       {
338         const LineRun* line = (lines + lineIndex);
339         if(line->ellipsis)
340         {
341           ellipsisLine = line;
342           if(lineIndex < numberOfLines - 1u)
343           {
344             ellipsisNextLine = (lines + lineIndex + 1u);
345           }
346           break;
347         }
348       }
349
350       // Check if there is a line contains Ellipsis.
351       // Then find total number of glyphs and total number of laid out glyphs.
352       // Check where to set Ellipsis glyph in line.
353       // Determine index of Ellipsis glyph and how many glyphs should be replaced by Ellipsis glyph, according to width of Ellipsis glyph.
354       if(ellipsisLine != nullptr)
355       {
356         // Total number of glyphs.
357         const Length numberOfGlyphs = mModel->GetNumberOfGlyphs();
358         // Total number of laid out glyphs.
359         Length numberOfActualLaidOutGlyphs = 0u;
360
361         // Accumulate laid out glyphs for each line to find total number of laid out glyphs.
362         for(Length lineIndex = 0u; lineIndex < numberOfLines; lineIndex++)
363         {
364           numberOfActualLaidOutGlyphs += lines[lineIndex].glyphRun.numberOfGlyphs + lines[lineIndex].glyphRunSecondHalf.numberOfGlyphs;
365         }
366
367         // Make sure there are laid out glyphs.
368         if(0u != numberOfActualLaidOutGlyphs)
369         {
370           // There are elided glyphs.
371           mIsTextElided                          = true;
372           TextAbstraction::FontClient fontClient = TextAbstraction::FontClient::Get();
373
374           // Retrieve the whole glyphs and their positions.
375           const GlyphInfo* const glyphs    = mModel->GetGlyphs();
376           const Vector2* const   positions = mModel->GetLayout();
377
378           // Copy the glyphs to be elided.
379           mElidedGlyphs.Resize(numberOfGlyphs);
380           mElidedLayout.Resize(numberOfGlyphs);
381           GlyphInfo* elidedGlyphsBuffer    = mElidedGlyphs.Begin();
382           Vector2*   elidedPositionsBuffer = mElidedLayout.Begin();
383
384           memcpy(elidedGlyphsBuffer, glyphs, numberOfGlyphs * sizeof(GlyphInfo));
385           memcpy(elidedPositionsBuffer, positions, numberOfGlyphs * sizeof(Vector2));
386
387           const Size& controlSize = mModel->GetControlSize();
388
389           // Set index where to set Ellipsis according to the selected position of Ellipsis.
390           // Start with this index to replace its glyph by Ellipsis, if the width  is not enough, then remove more glyphs.
391           GlyphIndex startIndexOfEllipsis = 0u;
392           if(ellipsisPosition == DevelText::EllipsisPosition::START)
393           {
394             // It's the fisrt glyph in line.
395             startIndexOfEllipsis = ellipsisLine->glyphRun.glyphIndex;
396           }
397           else if(ellipsisPosition == DevelText::EllipsisPosition::MIDDLE)
398           {
399             // It's the second middle of the line in case the line split to two halves.
400             // Otherwise it's It's the last glyph in line (line before all removed lines).
401             startIndexOfEllipsis = ellipsisLine->isSplitToTwoHalves ? (ellipsisLine->glyphRunSecondHalf.glyphIndex) : (ellipsisLine->glyphRun.glyphIndex + ellipsisLine->glyphRun.numberOfGlyphs - 1u);
402           }
403           else // DevelText::EllipsisPosition::END
404           {
405             // It's the last glyph in line.
406             startIndexOfEllipsis = ellipsisLine->glyphRun.glyphIndex + ellipsisLine->glyphRun.numberOfGlyphs - 1u;
407           }
408
409           // When the hight is not enough then show one glyph and that should be the first laid out glyph.
410           if((1u == numberOfLines) &&
411              (ellipsisLine->ascender - ellipsisLine->descender > controlSize.height))
412           {
413             // Replace the first glyph with ellipsis glyph
414             auto indexOfFirstGlyph = (ellipsisPosition == DevelText::EllipsisPosition::START) ? startIndexOfEllipsis : 0u;
415
416             // Regardless where the location of ellipsis,in-case the hight of line is greater than control's height
417             // then replace the first glyph with ellipsis glyph.
418
419             // Get the first glyph which is going to be replaced and the ellipsis glyph.
420             GlyphInfo&       glyphToRemove = *(elidedGlyphsBuffer + indexOfFirstGlyph);
421             const GlyphInfo& ellipsisGlyph = fontClient.GetEllipsisGlyph(fontClient.GetPointSize(glyphToRemove.fontId));
422
423             // Change the 'x' and 'y' position of the ellipsis glyph.
424             Vector2& position = *(elidedPositionsBuffer + indexOfFirstGlyph);
425
426             position.x = ellipsisGlyph.xBearing;
427             position.y = -ellipsisLine->ascender + controlSize.height - ellipsisGlyph.yBearing;
428
429             // Replace the glyph by the ellipsis glyph and resize the buffers.
430             glyphToRemove = ellipsisGlyph;
431
432             mElidedGlyphs.Resize(1u);
433             mElidedLayout.Resize(1u);
434
435             mEndIndexOfElidedGlyphs = mStartIndexOfElidedGlyphs = mFirstMiddleIndexOfElidedGlyphs = mSecondMiddleIndexOfElidedGlyphs = indexOfFirstGlyph;
436
437             return;
438           }
439
440           // firstPenX, penY and firstPenSet are used to position the ellipsis glyph if needed.
441           float firstPenX   = 0.f; // Used if rtl text is elided.
442           float penY        = 0.f;
443           bool  firstPenSet = false;
444
445           // Add the ellipsis glyph.
446           bool       inserted              = false;
447           float      removedGlypsWidth     = 0.f;
448           Length     numberOfRemovedGlyphs = 0u;
449           GlyphIndex indexOfEllipsis       = startIndexOfEllipsis;
450
451           // Tail Mode: start by the end of line.
452           bool isTailMode = (ellipsisPosition == DevelText::EllipsisPosition::END) ||
453                             (ellipsisPosition == DevelText::EllipsisPosition::MIDDLE && numberOfLines != 1u);
454
455           // The ellipsis glyph has to fit in the place where the last glyph(s) is(are) removed.
456           while(!inserted)
457           {
458             const GlyphInfo& glyphToRemove = *(elidedGlyphsBuffer + indexOfEllipsis);
459
460             if(0u != glyphToRemove.fontId)
461             {
462               // i.e. The font id of the glyph shaped from the '\n' character is zero.
463
464               // Need to reshape the glyph as the font may be different in size.
465               const GlyphInfo& ellipsisGlyph = fontClient.GetEllipsisGlyph(fontClient.GetPointSize(glyphToRemove.fontId));
466
467               if(!firstPenSet || EqualsZero(glyphToRemove.advance))
468               {
469                 const Vector2& position = *(elidedPositionsBuffer + indexOfEllipsis);
470
471                 // Calculates the penY of the current line. It will be used to position the ellipsis glyph.
472                 penY = position.y + glyphToRemove.yBearing;
473
474                 // Calculates the first penX which will be used if rtl text is elided.
475                 firstPenX = position.x - glyphToRemove.xBearing;
476                 if(firstPenX < -ellipsisGlyph.xBearing)
477                 {
478                   // Avoids to exceed the bounding box when rtl text is elided.
479                   firstPenX = -ellipsisGlyph.xBearing;
480                 }
481
482                 removedGlypsWidth = -ellipsisGlyph.xBearing;
483
484                 firstPenSet = true;
485               }
486
487               removedGlypsWidth += std::min(glyphToRemove.advance, (glyphToRemove.xBearing + glyphToRemove.width));
488
489               // Calculate the width of the ellipsis glyph and check if it fits.
490               const float ellipsisGlyphWidth = ellipsisGlyph.width + ellipsisGlyph.xBearing;
491
492               // If it is the last glyph to remove, add the ellipsis glyph without checking its width.
493               if((ellipsisGlyphWidth < removedGlypsWidth) || (isTailMode ? (indexOfEllipsis == 0u) : (indexOfEllipsis == numberOfGlyphs - 1u)))
494               {
495                 GlyphInfo& glyphInfo = *(elidedGlyphsBuffer + indexOfEllipsis);
496                 Vector2&   position  = *(elidedPositionsBuffer + indexOfEllipsis);
497                 position.x -= (0.f > glyphInfo.xBearing) ? glyphInfo.xBearing : 0.f;
498
499                 // Replace the glyph by the ellipsis glyph.
500                 glyphInfo = ellipsisGlyph;
501
502                 // Change the 'x' and 'y' position of the ellipsis glyph.
503                 if(position.x > firstPenX)
504                 {
505                   position.x = firstPenX + removedGlypsWidth - ellipsisGlyphWidth;
506                 }
507
508                 position.x += ellipsisGlyph.xBearing;
509                 position.y = penY - ellipsisGlyph.yBearing;
510
511                 inserted = true;
512               }
513             }
514
515             if(!inserted)
516             {
517               if(!isTailMode && indexOfEllipsis < numberOfGlyphs - 1u)
518               {
519                 // Tail Mode: remove glyphs from startIndexOfEllipsis then decrement indexOfEllipsis, until arrive to index zero.
520                 ++indexOfEllipsis;
521               }
522               else if(isTailMode && indexOfEllipsis > 0u)
523               {
524                 // Not Tail Mode: remove glyphs from startIndexOfEllipsis then increase indexOfEllipsis, until arrive to last index (numberOfGlyphs - 1u).
525                 --indexOfEllipsis;
526               }
527               else
528               {
529                 // No space for the ellipsis.
530                 inserted = true;
531               }
532               ++numberOfRemovedGlyphs;
533             }
534           } // while( !inserted )
535
536           //Reduce size, shift glyphs and start from ellipsis glyph
537           Length numberOfElidedGlyphs = numberOfActualLaidOutGlyphs - numberOfRemovedGlyphs;
538           mElidedGlyphs.Resize(numberOfElidedGlyphs);
539           mElidedLayout.Resize(numberOfElidedGlyphs);
540
541           if(ellipsisPosition == DevelText::EllipsisPosition::START)
542           {
543             // 'Shifts' glyphs after ellipsis glyph and 'Removes' before ellipsis glyph
544             memcpy(elidedGlyphsBuffer, elidedGlyphsBuffer + indexOfEllipsis, numberOfElidedGlyphs * sizeof(GlyphInfo));
545             memcpy(elidedPositionsBuffer, elidedPositionsBuffer + indexOfEllipsis, numberOfElidedGlyphs * sizeof(Vector2));
546
547             mStartIndexOfElidedGlyphs = mFirstMiddleIndexOfElidedGlyphs = mSecondMiddleIndexOfElidedGlyphs = indexOfEllipsis;
548           }
549           else if(ellipsisPosition == DevelText::EllipsisPosition::MIDDLE)
550           {
551             // 'Shifts and connects' glyphs before and after ellipsis glyph and 'Removes' in-between.
552             bool isOnlySecondHalf = false;
553
554             if(isTailMode)
555             {
556               mFirstMiddleIndexOfElidedGlyphs = indexOfEllipsis;
557               if(ellipsisNextLine != nullptr)
558               {
559                 mSecondMiddleIndexOfElidedGlyphs = ellipsisNextLine->glyphRun.glyphIndex;
560               }
561               else
562               {
563                 mEndIndexOfElidedGlyphs = mSecondMiddleIndexOfElidedGlyphs = mFirstMiddleIndexOfElidedGlyphs;
564               }
565             }
566             else
567             {
568               mFirstMiddleIndexOfElidedGlyphs  = (ellipsisLine->glyphRun.numberOfGlyphs > 0u) ? (ellipsisLine->glyphRun.glyphIndex + ellipsisLine->glyphRun.numberOfGlyphs - 1u) : (ellipsisLine->glyphRun.glyphIndex);
569               mSecondMiddleIndexOfElidedGlyphs = indexOfEllipsis;
570               isOnlySecondHalf                 = ellipsisLine->glyphRun.numberOfGlyphs == 0u && ellipsisLine->glyphRunSecondHalf.numberOfGlyphs > 0u;
571             }
572
573             if(isOnlySecondHalf)
574             {
575               Length numberOfSecondHalfGlyphs = numberOfElidedGlyphs - mFirstMiddleIndexOfElidedGlyphs;
576
577               //Copy elided glyphs after the ellipsis glyph.
578               memcpy(elidedGlyphsBuffer + mFirstMiddleIndexOfElidedGlyphs, elidedGlyphsBuffer + mSecondMiddleIndexOfElidedGlyphs, numberOfSecondHalfGlyphs * sizeof(GlyphInfo));
579               memcpy(elidedPositionsBuffer + mFirstMiddleIndexOfElidedGlyphs, elidedPositionsBuffer + mSecondMiddleIndexOfElidedGlyphs, numberOfSecondHalfGlyphs * sizeof(Vector2));
580             }
581             else
582             {
583               Length numberOfSecondHalfGlyphs = numberOfElidedGlyphs - mFirstMiddleIndexOfElidedGlyphs + 1u;
584
585               //Copy elided glyphs after the ellipsis glyph.
586               memcpy(elidedGlyphsBuffer + mFirstMiddleIndexOfElidedGlyphs + 1u, elidedGlyphsBuffer + mSecondMiddleIndexOfElidedGlyphs, numberOfSecondHalfGlyphs * sizeof(GlyphInfo));
587               memcpy(elidedPositionsBuffer + mFirstMiddleIndexOfElidedGlyphs + 1u, elidedPositionsBuffer + mSecondMiddleIndexOfElidedGlyphs, numberOfSecondHalfGlyphs * sizeof(Vector2));
588             }
589           }
590           else // DevelText::EllipsisPosition::END
591           {
592             // 'Removes' all the glyphs after the ellipsis glyph.
593             mEndIndexOfElidedGlyphs = indexOfEllipsis;
594           }
595         }
596       }
597     }
598   }
599 }
600
601 float ViewModel::GetStrikethroughHeight() const
602 {
603   return mModel->GetStrikethroughHeight();
604 }
605
606 const Vector4& ViewModel::GetStrikethroughColor() const
607 {
608   return mModel->GetStrikethroughColor();
609 }
610
611 bool ViewModel::IsStrikethroughEnabled() const
612 {
613   return mModel->IsStrikethroughEnabled();
614 }
615
616 Length ViewModel::GetNumberOfStrikethroughRuns() const
617 {
618   return mModel->GetNumberOfStrikethroughRuns();
619 }
620
621 void ViewModel::GetStrikethroughRuns(StrikethroughGlyphRun* strikethroughRuns, StrikethroughRunIndex index, Length numberOfRuns) const
622 {
623   mModel->GetStrikethroughRuns(strikethroughRuns, index, numberOfRuns);
624 }
625
626 } // namespace Text
627
628 } // namespace Toolkit
629
630 } // namespace Dali