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