Fix corrupted markup background
[platform/core/uifw/dali-toolkit.git] / dali-toolkit / internal / text / text-view.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/text-view.h>
20
21 // EXTERNAL INCLUDES
22 #include <dali/devel-api/text-abstraction/font-client.h>
23 #include <dali/public-api/math/vector2.h>
24
25 namespace Dali
26 {
27 namespace Toolkit
28 {
29 namespace Text
30 {
31 struct View::Impl
32 {
33   VisualModelPtr              mVisualModel;
34   TextAbstraction::FontClient mFontClient; ///< Handle to the font client.
35 };
36
37 View::View()
38 : mImpl(NULL)
39 {
40   mImpl = new View::Impl();
41
42   mImpl->mFontClient = TextAbstraction::FontClient::Get();
43 }
44
45 View::~View()
46 {
47   delete mImpl;
48 }
49
50 void View::SetVisualModel(VisualModelPtr visualModel)
51 {
52   mImpl->mVisualModel = visualModel;
53 }
54
55 const Vector2& View::GetControlSize() const
56 {
57   if(mImpl->mVisualModel)
58   {
59     return mImpl->mVisualModel->mControlSize;
60   }
61
62   return Vector2::ZERO;
63 }
64
65 const Vector2& View::GetLayoutSize() const
66 {
67   if(mImpl->mVisualModel)
68   {
69     return mImpl->mVisualModel->GetLayoutSize();
70   }
71
72   return Vector2::ZERO;
73 }
74
75 Length View::GetNumberOfGlyphs() const
76 {
77   if(mImpl->mVisualModel)
78   {
79     const VisualModel& model = *mImpl->mVisualModel;
80
81     const Length glyphCount    = model.mGlyphs.Count();
82     const Length positionCount = model.mGlyphPositions.Count();
83
84     DALI_ASSERT_DEBUG(positionCount <= glyphCount && "Invalid glyph positions in Model");
85
86     return (positionCount < glyphCount) ? positionCount : glyphCount;
87   }
88
89   return 0;
90 }
91
92 Length View::GetGlyphs(GlyphInfo* glyphs,
93                        Vector2*   glyphPositions,
94                        float&     minLineOffset,
95                        GlyphIndex glyphIndex,
96                        Length     numberOfGlyphs) const
97 {
98   Length numberOfLaidOutGlyphs = 0u;
99
100   if(mImpl->mVisualModel)
101   {
102     // If ellipsis is enabled, the number of glyphs the layout engine has laid out may be less than 'numberOfGlyphs'.
103     // Check the last laid out line to know if the layout engine elided some text.
104
105     const Length numberOfLines = mImpl->mVisualModel->mLines.Count();
106     if(numberOfLines > 0u)
107     {
108       const LineRun& lastLine = *(mImpl->mVisualModel->mLines.Begin() + (numberOfLines - 1u));
109
110       // If ellipsis is enabled, calculate the number of laid out glyphs.
111       // Otherwise use the given number of glyphs.
112       if(lastLine.ellipsis)
113       {
114         numberOfLaidOutGlyphs = lastLine.glyphRun.glyphIndex + lastLine.glyphRun.numberOfGlyphs;
115       }
116       else
117       {
118         numberOfLaidOutGlyphs = numberOfGlyphs;
119       }
120
121       if(0u < numberOfLaidOutGlyphs)
122       {
123         // Retrieve from the visual model the glyphs and positions.
124         mImpl->mVisualModel->GetGlyphs(glyphs,
125                                        glyphIndex,
126                                        numberOfLaidOutGlyphs);
127
128         mImpl->mVisualModel->GetGlyphPositions(glyphPositions,
129                                                glyphIndex,
130                                                numberOfLaidOutGlyphs);
131
132         // Get the lines for the given range of glyphs.
133         // The lines contain the alignment offset which needs to be added to the glyph's position.
134         LineIndex firstLine     = 0u;
135         Length    numberOfLines = 0u;
136         mImpl->mVisualModel->GetNumberOfLines(glyphIndex,
137                                               numberOfLaidOutGlyphs,
138                                               firstLine,
139                                               numberOfLines);
140
141         Vector<LineRun> lines;
142         lines.Resize(numberOfLines);
143         LineRun* lineBuffer = lines.Begin();
144
145         mImpl->mVisualModel->GetLinesOfGlyphRange(lineBuffer,
146                                                   glyphIndex,
147                                                   numberOfLaidOutGlyphs);
148
149         // Get the first line for the given glyph range.
150         LineIndex lineIndex = firstLine;
151         LineRun*  line      = lineBuffer + lineIndex;
152
153         // Index of the last glyph of the line.
154         GlyphIndex lastGlyphIndexOfLine = line->glyphRun.glyphIndex + line->glyphRun.numberOfGlyphs - 1u;
155
156         // Add the alignment offset to the glyph's position.
157
158         minLineOffset = line->alignmentOffset;
159         float penY    = line->ascender;
160         for(Length index = 0u; index < numberOfLaidOutGlyphs; ++index)
161         {
162           Vector2& position = *(glyphPositions + index);
163           position.x += line->alignmentOffset;
164           position.y += penY;
165
166           if(lastGlyphIndexOfLine == index)
167           {
168             penY += -line->descender;
169
170             // Get the next line.
171             ++lineIndex;
172
173             if(lineIndex < numberOfLines)
174             {
175               line          = lineBuffer + lineIndex;
176               minLineOffset = std::min(minLineOffset, line->alignmentOffset);
177
178               lastGlyphIndexOfLine = line->glyphRun.glyphIndex + line->glyphRun.numberOfGlyphs - 1u;
179
180               penY += line->ascender;
181             }
182           }
183         }
184
185         if(1u == numberOfLaidOutGlyphs)
186         {
187           // not a point try to do ellipsis with only one laid out character.
188           return numberOfLaidOutGlyphs;
189         }
190
191         if(lastLine.ellipsis)
192         {
193           if((1u == numberOfLines) &&
194              (lastLine.ascender - lastLine.descender > mImpl->mVisualModel->mControlSize.height))
195           {
196             // Get the first glyph which is going to be replaced and the ellipsis glyph.
197             GlyphInfo&       glyphInfo     = *glyphs;
198             const GlyphInfo& ellipsisGlyph = mImpl->mFontClient.GetEllipsisGlyph(mImpl->mFontClient.GetPointSize(glyphInfo.fontId));
199
200             // Change the 'x' and 'y' position of the ellipsis glyph.
201             Vector2& position = *glyphPositions;
202             position.x        = ellipsisGlyph.xBearing;
203             position.y        = mImpl->mVisualModel->mControlSize.height - ellipsisGlyph.yBearing;
204
205             // Replace the glyph by the ellipsis glyph.
206             glyphInfo = ellipsisGlyph;
207
208             return 1u;
209           }
210
211           // firstPenX, penY and firstPenSet are used to position the ellipsis glyph if needed.
212           float firstPenX   = 0.f; // Used if rtl text is elided.
213           float penY        = 0.f;
214           bool  firstPenSet = false;
215
216           // Add the ellipsis glyph.
217           bool       inserted              = false;
218           float      removedGlypsWidth     = 0.f;
219           Length     numberOfRemovedGlyphs = 0u;
220           GlyphIndex index                 = numberOfLaidOutGlyphs - 1u;
221
222           // The ellipsis glyph has to fit in the place where the last glyph(s) is(are) removed.
223           while(!inserted)
224           {
225             const GlyphInfo& glyphToRemove = *(glyphs + index);
226
227             if(0u != glyphToRemove.fontId)
228             {
229               // i.e. The font id of the glyph shaped from the '\n' character is zero.
230
231               // Need to reshape the glyph as the font may be different in size.
232               const GlyphInfo& ellipsisGlyph = mImpl->mFontClient.GetEllipsisGlyph(mImpl->mFontClient.GetPointSize(glyphToRemove.fontId));
233
234               if(!firstPenSet)
235               {
236                 const Vector2& position = *(glyphPositions + index);
237
238                 // Calculates the penY of the current line. It will be used to position the ellipsis glyph.
239                 penY = position.y + glyphToRemove.yBearing;
240
241                 // Calculates the first penX which will be used if rtl text is elided.
242                 firstPenX = position.x - glyphToRemove.xBearing;
243                 if(firstPenX < -ellipsisGlyph.xBearing)
244                 {
245                   // Avoids to exceed the bounding box when rtl text is elided.
246                   firstPenX = -ellipsisGlyph.xBearing;
247                 }
248
249                 removedGlypsWidth = -ellipsisGlyph.xBearing;
250
251                 firstPenSet = true;
252               }
253
254               removedGlypsWidth += std::min(glyphToRemove.advance, (glyphToRemove.xBearing + glyphToRemove.width));
255
256               // Calculate the width of the ellipsis glyph and check if it fits.
257               const float ellipsisGlyphWidth = ellipsisGlyph.width + ellipsisGlyph.xBearing;
258               if(ellipsisGlyphWidth < removedGlypsWidth)
259               {
260                 GlyphInfo& glyphInfo = *(glyphs + index);
261                 Vector2&   position  = *(glyphPositions + index);
262                 position.x -= (0.f > glyphInfo.xBearing) ? glyphInfo.xBearing : 0.f;
263
264                 // Replace the glyph by the ellipsis glyph.
265                 glyphInfo = ellipsisGlyph;
266
267                 // Change the 'x' and 'y' position of the ellipsis glyph.
268
269                 if(position.x > firstPenX)
270                 {
271                   position.x = firstPenX + removedGlypsWidth - ellipsisGlyphWidth;
272                 }
273
274                 position.x += ellipsisGlyph.xBearing;
275                 position.y = penY - ellipsisGlyph.yBearing;
276
277                 inserted = true;
278               }
279             }
280
281             if(!inserted)
282             {
283               if(index > 0u)
284               {
285                 --index;
286               }
287               else
288               {
289                 // No space for the ellipsis.
290                 inserted = true;
291               }
292               ++numberOfRemovedGlyphs;
293             }
294           }
295
296           // 'Removes' all the glyphs after the ellipsis glyph.
297           numberOfLaidOutGlyphs -= numberOfRemovedGlyphs;
298         }
299       }
300     }
301   }
302
303   return numberOfLaidOutGlyphs;
304 }
305
306 const Vector4* const View::GetColors() const
307 {
308   if(mImpl->mVisualModel)
309   {
310     return mImpl->mVisualModel->mColors.Begin();
311   }
312
313   return NULL;
314 }
315
316 const ColorIndex* const View::GetColorIndices() const
317 {
318   if(mImpl->mVisualModel)
319   {
320     return mImpl->mVisualModel->mColorIndices.Begin();
321   }
322
323   return NULL;
324 }
325
326 const Vector4* const View::GetBackgroundColors() const
327 {
328   if(mImpl->mVisualModel)
329   {
330     return mImpl->mVisualModel->mBackgroundColors.Begin();
331   }
332
333   return nullptr;
334 }
335
336 const ColorIndex* const View::GetBackgroundColorIndices() const
337 {
338   if(mImpl->mVisualModel)
339   {
340     return mImpl->mVisualModel->mBackgroundColorIndices.Begin();
341   }
342
343   return nullptr;
344 }
345
346 bool const View::IsMarkupBackgroundColorSet() const
347 {
348   if(mImpl->mVisualModel)
349   {
350     return (mImpl->mVisualModel->mBackgroundColors.Count() > 0);
351   }
352
353   return false;
354 }
355
356 const Vector4& View::GetTextColor() const
357 {
358   if(mImpl->mVisualModel)
359   {
360     return mImpl->mVisualModel->GetTextColor();
361   }
362   return Vector4::ZERO;
363 }
364
365 const Vector2& View::GetShadowOffset() const
366 {
367   if(mImpl->mVisualModel)
368   {
369     return mImpl->mVisualModel->GetShadowOffset();
370   }
371   return Vector2::ZERO;
372 }
373
374 const Vector4& View::GetShadowColor() const
375 {
376   if(mImpl->mVisualModel)
377   {
378     return mImpl->mVisualModel->GetShadowColor();
379   }
380   return Vector4::ZERO;
381 }
382
383 const Vector4& View::GetUnderlineColor() const
384 {
385   if(mImpl->mVisualModel)
386   {
387     return mImpl->mVisualModel->GetUnderlineColor();
388   }
389   return Vector4::ZERO;
390 }
391
392 bool View::IsUnderlineEnabled() const
393 {
394   if(mImpl->mVisualModel)
395   {
396     return mImpl->mVisualModel->IsUnderlineEnabled();
397   }
398   return false;
399 }
400
401 const GlyphInfo* View::GetHyphens() const
402 {
403   if(mImpl->mVisualModel)
404   {
405     return mImpl->mVisualModel->mHyphen.glyph.Begin();
406   }
407
408   return nullptr;
409 }
410
411 const Length* View::GetHyphenIndices() const
412 {
413   if(mImpl->mVisualModel)
414   {
415     return mImpl->mVisualModel->mHyphen.index.Begin();
416   }
417
418   return nullptr;
419 }
420
421 Length View::GetHyphensCount() const
422 {
423   if(mImpl->mVisualModel)
424   {
425     return mImpl->mVisualModel->mHyphen.glyph.Size();
426   }
427
428   return 0;
429 }
430 float View::GetUnderlineHeight() const
431 {
432   if(mImpl->mVisualModel)
433   {
434     return mImpl->mVisualModel->GetUnderlineHeight();
435   }
436   return 0.0f;
437 }
438
439 Length View::GetNumberOfUnderlineRuns() const
440 {
441   if(mImpl->mVisualModel)
442   {
443     return mImpl->mVisualModel->GetNumberOfUnderlineRuns();
444   }
445
446   return 0u;
447 }
448
449 void View::GetUnderlineRuns(GlyphRun*         underlineRuns,
450                             UnderlineRunIndex index,
451                             Length            numberOfRuns) const
452 {
453   if(mImpl->mVisualModel)
454   {
455     mImpl->mVisualModel->GetUnderlineRuns(underlineRuns,
456                                           index,
457                                           numberOfRuns);
458   }
459 }
460
461 const Vector4& View::GetOutlineColor() const
462 {
463   if(mImpl->mVisualModel)
464   {
465     return mImpl->mVisualModel->GetOutlineColor();
466   }
467   return Vector4::ZERO;
468 }
469
470 uint16_t View::GetOutlineWidth() const
471 {
472   if(mImpl->mVisualModel)
473   {
474     return mImpl->mVisualModel->GetOutlineWidth();
475   }
476   return 0u;
477 }
478
479 } // namespace Text
480
481 } // namespace Toolkit
482
483 } // namespace Dali