83ad28fd8aba8e4bcc8b824c6a8a7b2e1083103c
[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 const Vector4& View::GetTextColor() const
347 {
348   if(mImpl->mVisualModel)
349   {
350     return mImpl->mVisualModel->GetTextColor();
351   }
352   return Vector4::ZERO;
353 }
354
355 const Vector2& View::GetShadowOffset() const
356 {
357   if(mImpl->mVisualModel)
358   {
359     return mImpl->mVisualModel->GetShadowOffset();
360   }
361   return Vector2::ZERO;
362 }
363
364 const Vector4& View::GetShadowColor() const
365 {
366   if(mImpl->mVisualModel)
367   {
368     return mImpl->mVisualModel->GetShadowColor();
369   }
370   return Vector4::ZERO;
371 }
372
373 const Vector4& View::GetUnderlineColor() const
374 {
375   if(mImpl->mVisualModel)
376   {
377     return mImpl->mVisualModel->GetUnderlineColor();
378   }
379   return Vector4::ZERO;
380 }
381
382 bool View::IsUnderlineEnabled() const
383 {
384   if(mImpl->mVisualModel)
385   {
386     return mImpl->mVisualModel->IsUnderlineEnabled();
387   }
388   return false;
389 }
390
391 float View::GetUnderlineHeight() const
392 {
393   if(mImpl->mVisualModel)
394   {
395     return mImpl->mVisualModel->GetUnderlineHeight();
396   }
397   return 0.0f;
398 }
399
400 Length View::GetNumberOfUnderlineRuns() const
401 {
402   if(mImpl->mVisualModel)
403   {
404     return mImpl->mVisualModel->GetNumberOfUnderlineRuns();
405   }
406
407   return 0u;
408 }
409
410 void View::GetUnderlineRuns(GlyphRun*         underlineRuns,
411                             UnderlineRunIndex index,
412                             Length            numberOfRuns) const
413 {
414   if(mImpl->mVisualModel)
415   {
416     mImpl->mVisualModel->GetUnderlineRuns(underlineRuns,
417                                           index,
418                                           numberOfRuns);
419   }
420 }
421
422 const Vector4& View::GetOutlineColor() const
423 {
424   if(mImpl->mVisualModel)
425   {
426     return mImpl->mVisualModel->GetOutlineColor();
427   }
428   return Vector4::ZERO;
429 }
430
431 uint16_t View::GetOutlineWidth() const
432 {
433   if(mImpl->mVisualModel)
434   {
435     return mImpl->mVisualModel->GetOutlineWidth();
436   }
437   return 0u;
438 }
439
440 } // namespace Text
441
442 } // namespace Toolkit
443
444 } // namespace Dali