[dali_2.0.35] Merge branch 'devel/master'
[platform/core/uifw/dali-toolkit.git] / dali-toolkit / internal / text / rendering / view-model.cpp
1 /*
2  * Copyright (c) 2021 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 Length ViewModel::GetNumberOfUnderlineRuns() const
253 {
254   return mModel->GetNumberOfUnderlineRuns();
255 }
256
257 void ViewModel::GetUnderlineRuns(GlyphRun* underlineRuns, UnderlineRunIndex index, Length numberOfRuns) const
258 {
259   mModel->GetUnderlineRuns(underlineRuns, index, numberOfRuns);
260 }
261
262 const Vector4& ViewModel::GetOutlineColor() const
263 {
264   return mModel->GetOutlineColor();
265 }
266
267 uint16_t ViewModel::GetOutlineWidth() const
268 {
269   return mModel->GetOutlineWidth();
270 }
271
272 const Vector4& ViewModel::GetBackgroundColor() const
273 {
274   return mModel->GetBackgroundColor();
275 }
276
277 bool ViewModel::IsBackgroundEnabled() const
278 {
279   return mModel->IsBackgroundEnabled();
280 }
281
282 bool ViewModel::IsMarkupProcessorEnabled() const
283 {
284   return mModel->IsMarkupProcessorEnabled();
285 }
286
287 const GlyphInfo* ViewModel::GetHyphens() const
288 {
289   return mModel->GetHyphens();
290 }
291
292 const Length* ViewModel::GetHyphenIndices() const
293 {
294   return mModel->GetHyphenIndices();
295 }
296
297 Length ViewModel::GetHyphensCount() const
298 {
299   return mModel->GetHyphensCount();
300 }
301
302 void ViewModel::ElideGlyphs()
303 {
304   mIsTextElided             = false;
305   mStartIndexOfElidedGlyphs = mFirstMiddleIndexOfElidedGlyphs = mSecondMiddleIndexOfElidedGlyphs = 0;
306   mEndIndexOfElidedGlyphs                                                                        = mModel->GetNumberOfGlyphs() - 1u;
307
308   auto ellipsisPosition = GetEllipsisPosition();
309
310   if(IsTextElideEnabled())
311   {
312     const Length numberOfLines = mModel->GetNumberOfLines();
313     if(0u != numberOfLines)
314     {
315       const LineRun* const lines = mModel->GetLines();
316
317       //Get line of ellipsis
318       const LineRun* ellipsisLine     = nullptr;
319       const LineRun* ellipsisNextLine = nullptr;
320
321       for(Length lineIndex = 0; lineIndex < numberOfLines; lineIndex++)
322       {
323         const LineRun* line = (lines + lineIndex);
324         if(line->ellipsis)
325         {
326           ellipsisLine = line;
327           if(lineIndex < numberOfLines - 1u)
328           {
329             ellipsisNextLine = (lines + lineIndex + 1u);
330           }
331           break;
332         }
333       }
334
335       // Check if there is a line contains Ellipsis.
336       // Then find total number of glyphs and total number of laid out glyphs.
337       // Check where to set Ellipsis glyph in line.
338       // Determine index of Ellipsis glyph and how many glyphs should be replaced by Ellipsis glyph, according to width of Ellipsis glyph.
339       if(ellipsisLine != nullptr)
340       {
341         // Total number of glyphs.
342         const Length numberOfGlyphs = mModel->GetNumberOfGlyphs();
343         // Total number of laid out glyphs.
344         Length numberOfActualLaidOutGlyphs = 0u;
345
346         // Accumulate laid out glyphs for each line to find total number of laid out glyphs.
347         for(Length lineIndex = 0u; lineIndex < numberOfLines; lineIndex++)
348         {
349           numberOfActualLaidOutGlyphs += lines[lineIndex].glyphRun.numberOfGlyphs + lines[lineIndex].glyphRunSecondHalf.numberOfGlyphs;
350         }
351
352         // Make sure there are laid out glyphs.
353         if(0u != numberOfActualLaidOutGlyphs)
354         {
355           // There are elided glyphs.
356           mIsTextElided                          = true;
357           TextAbstraction::FontClient fontClient = TextAbstraction::FontClient::Get();
358
359           // Retrieve the whole glyphs and their positions.
360           const GlyphInfo* const glyphs    = mModel->GetGlyphs();
361           const Vector2* const   positions = mModel->GetLayout();
362
363           // Copy the glyphs to be elided.
364           mElidedGlyphs.Resize(numberOfGlyphs);
365           mElidedLayout.Resize(numberOfGlyphs);
366           GlyphInfo* elidedGlyphsBuffer    = mElidedGlyphs.Begin();
367           Vector2*   elidedPositionsBuffer = mElidedLayout.Begin();
368
369           memcpy(elidedGlyphsBuffer, glyphs, numberOfGlyphs * sizeof(GlyphInfo));
370           memcpy(elidedPositionsBuffer, positions, numberOfGlyphs * sizeof(Vector2));
371
372           const Size& controlSize = mModel->GetControlSize();
373
374           // Set index where to set Ellipsis according to the selected position of Ellipsis.
375           // Start with this index to replace its glyph by Ellipsis, if the width  is not enough, then remove more glyphs.
376           GlyphIndex startIndexOfEllipsis = 0u;
377           if(ellipsisPosition == DevelText::EllipsisPosition::START)
378           {
379             // It's the fisrt glyph in line.
380             startIndexOfEllipsis = ellipsisLine->glyphRun.glyphIndex;
381           }
382           else if(ellipsisPosition == DevelText::EllipsisPosition::MIDDLE)
383           {
384             // It's the second middle of the line in case the line split to two halves.
385             // Otherwise it's It's the last glyph in line (line before all removed lines).
386             startIndexOfEllipsis = ellipsisLine->isSplitToTwoHalves ? (ellipsisLine->glyphRunSecondHalf.glyphIndex) : (ellipsisLine->glyphRun.glyphIndex + ellipsisLine->glyphRun.numberOfGlyphs - 1u);
387           }
388           else // DevelText::EllipsisPosition::END
389           {
390             // It's the last glyph in line.
391             startIndexOfEllipsis = ellipsisLine->glyphRun.glyphIndex + ellipsisLine->glyphRun.numberOfGlyphs - 1u;
392           }
393
394           // When the hight is not enough then show one glyph and that should be the first laid out glyph.
395           if((1u == numberOfLines) &&
396              (ellipsisLine->ascender - ellipsisLine->descender > controlSize.height))
397           {
398             // Replace the first glyph with ellipsis glyph
399             auto indexOfFirstGlyph = (ellipsisPosition == DevelText::EllipsisPosition::START) ? startIndexOfEllipsis : 0u;
400
401             // Regardless where the location of ellipsis,in-case the hight of line is greater than control's height
402             // then replace the first glyph with ellipsis glyph.
403
404             // Get the first glyph which is going to be replaced and the ellipsis glyph.
405             GlyphInfo&       glyphToRemove = *(elidedGlyphsBuffer + indexOfFirstGlyph);
406             const GlyphInfo& ellipsisGlyph = fontClient.GetEllipsisGlyph(fontClient.GetPointSize(glyphToRemove.fontId));
407
408             // Change the 'x' and 'y' position of the ellipsis glyph.
409             Vector2& position = *(elidedPositionsBuffer + indexOfFirstGlyph);
410
411             position.x = ellipsisGlyph.xBearing;
412             position.y = -ellipsisLine->ascender + controlSize.height - ellipsisGlyph.yBearing;
413
414             // Replace the glyph by the ellipsis glyph and resize the buffers.
415             glyphToRemove = ellipsisGlyph;
416
417             mElidedGlyphs.Resize(1u);
418             mElidedLayout.Resize(1u);
419
420             mEndIndexOfElidedGlyphs = mStartIndexOfElidedGlyphs = mFirstMiddleIndexOfElidedGlyphs = mSecondMiddleIndexOfElidedGlyphs = indexOfFirstGlyph;
421
422             return;
423           }
424
425           // firstPenX, penY and firstPenSet are used to position the ellipsis glyph if needed.
426           float firstPenX   = 0.f; // Used if rtl text is elided.
427           float penY        = 0.f;
428           bool  firstPenSet = false;
429
430           // Add the ellipsis glyph.
431           bool       inserted              = false;
432           float      removedGlypsWidth     = 0.f;
433           Length     numberOfRemovedGlyphs = 0u;
434           GlyphIndex indexOfEllipsis       = startIndexOfEllipsis;
435
436           // Tail Mode: start by the end of line.
437           bool isTailMode = (ellipsisPosition == DevelText::EllipsisPosition::END) ||
438                             (ellipsisPosition == DevelText::EllipsisPosition::MIDDLE && numberOfLines != 1u);
439
440           // The ellipsis glyph has to fit in the place where the last glyph(s) is(are) removed.
441           while(!inserted)
442           {
443             const GlyphInfo& glyphToRemove = *(elidedGlyphsBuffer + indexOfEllipsis);
444
445             if(0u != glyphToRemove.fontId)
446             {
447               // i.e. The font id of the glyph shaped from the '\n' character is zero.
448
449               // Need to reshape the glyph as the font may be different in size.
450               const GlyphInfo& ellipsisGlyph = fontClient.GetEllipsisGlyph(fontClient.GetPointSize(glyphToRemove.fontId));
451
452               if(!firstPenSet)
453               {
454                 const Vector2& position = *(elidedPositionsBuffer + indexOfEllipsis);
455
456                 // Calculates the penY of the current line. It will be used to position the ellipsis glyph.
457                 penY = position.y + glyphToRemove.yBearing;
458
459                 // Calculates the first penX which will be used if rtl text is elided.
460                 firstPenX = position.x - glyphToRemove.xBearing;
461                 if(firstPenX < -ellipsisGlyph.xBearing)
462                 {
463                   // Avoids to exceed the bounding box when rtl text is elided.
464                   firstPenX = -ellipsisGlyph.xBearing;
465                 }
466
467                 removedGlypsWidth = -ellipsisGlyph.xBearing;
468
469                 firstPenSet = true;
470               }
471
472               removedGlypsWidth += std::min(glyphToRemove.advance, (glyphToRemove.xBearing + glyphToRemove.width));
473
474               // Calculate the width of the ellipsis glyph and check if it fits.
475               const float ellipsisGlyphWidth = ellipsisGlyph.width + ellipsisGlyph.xBearing;
476
477               // If it is the last glyph to remove, add the ellipsis glyph without checking its width.
478               if((ellipsisGlyphWidth < removedGlypsWidth) || (isTailMode ? (indexOfEllipsis == 0u) : (indexOfEllipsis == numberOfGlyphs - 1u)))
479               {
480                 GlyphInfo& glyphInfo = *(elidedGlyphsBuffer + indexOfEllipsis);
481                 Vector2&   position  = *(elidedPositionsBuffer + indexOfEllipsis);
482                 position.x -= (0.f > glyphInfo.xBearing) ? glyphInfo.xBearing : 0.f;
483
484                 // Replace the glyph by the ellipsis glyph.
485                 glyphInfo = ellipsisGlyph;
486
487                 // Change the 'x' and 'y' position of the ellipsis glyph.
488                 if(position.x > firstPenX)
489                 {
490                   position.x = firstPenX + removedGlypsWidth - ellipsisGlyphWidth;
491                 }
492
493                 position.x += ellipsisGlyph.xBearing;
494                 position.y = penY - ellipsisGlyph.yBearing;
495
496                 inserted = true;
497               }
498             }
499
500             if(!inserted)
501             {
502               if(!isTailMode && indexOfEllipsis < numberOfGlyphs - 1u)
503               {
504                 // Tail Mode: remove glyphs from startIndexOfEllipsis then decrement indexOfEllipsis, until arrive to index zero.
505                 ++indexOfEllipsis;
506               }
507               else if(isTailMode && indexOfEllipsis > 0u)
508               {
509                 // Not Tail Mode: remove glyphs from startIndexOfEllipsis then increase indexOfEllipsis, until arrive to last index (numberOfGlyphs - 1u).
510                 --indexOfEllipsis;
511               }
512               else
513               {
514                 // No space for the ellipsis.
515                 inserted = true;
516               }
517               ++numberOfRemovedGlyphs;
518             }
519           } // while( !inserted )
520
521           //Reduce size, shift glyphs and start from ellipsis glyph
522           Length numberOfElidedGlyphs = numberOfActualLaidOutGlyphs - numberOfRemovedGlyphs;
523           mElidedGlyphs.Resize(numberOfElidedGlyphs);
524           mElidedLayout.Resize(numberOfElidedGlyphs);
525
526           if(ellipsisPosition == DevelText::EllipsisPosition::START)
527           {
528             // 'Shifts' glyphs after ellipsis glyph and 'Removes' before ellipsis glyph
529             memcpy(elidedGlyphsBuffer, elidedGlyphsBuffer + indexOfEllipsis, numberOfElidedGlyphs * sizeof(GlyphInfo));
530             memcpy(elidedPositionsBuffer, elidedPositionsBuffer + indexOfEllipsis, numberOfElidedGlyphs * sizeof(Vector2));
531
532             mStartIndexOfElidedGlyphs = mFirstMiddleIndexOfElidedGlyphs = mSecondMiddleIndexOfElidedGlyphs = indexOfEllipsis;
533           }
534           else if(ellipsisPosition == DevelText::EllipsisPosition::MIDDLE)
535           {
536             // 'Shifts and connects' glyphs before and after ellipsis glyph and 'Removes' in-between.
537             bool isOnlySecondHalf = false;
538
539             if(isTailMode)
540             {
541               mFirstMiddleIndexOfElidedGlyphs = indexOfEllipsis;
542               if(ellipsisNextLine != nullptr)
543               {
544                 mSecondMiddleIndexOfElidedGlyphs = ellipsisNextLine->glyphRun.glyphIndex;
545               }
546               else
547               {
548                 mEndIndexOfElidedGlyphs = mSecondMiddleIndexOfElidedGlyphs = mFirstMiddleIndexOfElidedGlyphs;
549               }
550             }
551             else
552             {
553               mFirstMiddleIndexOfElidedGlyphs  = (ellipsisLine->glyphRun.numberOfGlyphs > 0u) ? (ellipsisLine->glyphRun.glyphIndex + ellipsisLine->glyphRun.numberOfGlyphs - 1u) : (ellipsisLine->glyphRun.glyphIndex);
554               mSecondMiddleIndexOfElidedGlyphs = indexOfEllipsis;
555               isOnlySecondHalf                 = ellipsisLine->glyphRun.numberOfGlyphs == 0u && ellipsisLine->glyphRunSecondHalf.numberOfGlyphs > 0u;
556             }
557
558             if(isOnlySecondHalf)
559             {
560               Length numberOfSecondHalfGlyphs = numberOfElidedGlyphs - mFirstMiddleIndexOfElidedGlyphs;
561
562               //Copy elided glyphs after the ellipsis glyph.
563               memcpy(elidedGlyphsBuffer + mFirstMiddleIndexOfElidedGlyphs, elidedGlyphsBuffer + mSecondMiddleIndexOfElidedGlyphs, numberOfSecondHalfGlyphs * sizeof(GlyphInfo));
564               memcpy(elidedPositionsBuffer + mFirstMiddleIndexOfElidedGlyphs, elidedPositionsBuffer + mSecondMiddleIndexOfElidedGlyphs, numberOfSecondHalfGlyphs * sizeof(Vector2));
565             }
566             else
567             {
568               Length numberOfSecondHalfGlyphs = numberOfElidedGlyphs - mFirstMiddleIndexOfElidedGlyphs + 1u;
569
570               //Copy elided glyphs after the ellipsis glyph.
571               memcpy(elidedGlyphsBuffer + mFirstMiddleIndexOfElidedGlyphs + 1u, elidedGlyphsBuffer + mSecondMiddleIndexOfElidedGlyphs, numberOfSecondHalfGlyphs * sizeof(GlyphInfo));
572               memcpy(elidedPositionsBuffer + mFirstMiddleIndexOfElidedGlyphs + 1u, elidedPositionsBuffer + mSecondMiddleIndexOfElidedGlyphs, numberOfSecondHalfGlyphs * sizeof(Vector2));
573             }
574           }
575           else // DevelText::EllipsisPosition::END
576           {
577             // 'Removes' all the glyphs after the ellipsis glyph.
578             mEndIndexOfElidedGlyphs = indexOfEllipsis;
579           }
580         }
581       }
582     }
583   }
584 }
585
586 } // namespace Text
587
588 } // namespace Toolkit
589
590 } // namespace Dali