Fix corrupted markup background
[platform/core/uifw/dali-toolkit.git] / dali-toolkit / internal / text / rendering / text-typesetter.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/text-typesetter.h>
20
21 // EXTERNAL INCLUDES
22 #include <dali/devel-api/text-abstraction/font-client.h>
23 #include <dali/public-api/common/constants.h>
24 #include <memory.h>
25
26 // INTERNAL INCLUDES
27 #include <dali-toolkit/devel-api/controls/text-controls/text-label-devel.h>
28 #include <dali-toolkit/internal/text/rendering/view-model.h>
29
30 namespace Dali
31 {
32 namespace Toolkit
33 {
34 namespace Text
35 {
36 namespace
37 {
38 /**
39  * @brief Data struct used to set the buffer of the glyph's bitmap into the final bitmap's buffer.
40  */
41 struct GlyphData
42 {
43   Devel::PixelBuffer                           bitmapBuffer;     ///< The buffer of the whole bitmap. The format is RGBA8888.
44   Vector2*                                     position;         ///< The position of the glyph.
45   TextAbstraction::FontClient::GlyphBufferData glyphBitmap;      ///< The glyph's bitmap.
46   unsigned int                                 width;            ///< The bitmap's width.
47   unsigned int                                 height;           ///< The bitmap's height.
48   int                                          horizontalOffset; ///< The horizontal offset to be added to the 'x' glyph's position.
49   int                                          verticalOffset;   ///< The vertical offset to be added to the 'y' glyph's position.
50 };
51
52 /**
53  * @brief Sets the glyph's buffer into the bitmap's buffer.
54  *
55  * @param[in] data Struct which contains the glyph's data and the bitmap's data.
56  * @param[in] position The position of the glyph.
57  * @param[in] color The color of the glyph.
58  * @param[in] style The style of the text.
59  * @param[in] pixelFormat The format of the pixel in the image that the text is rendered as (i.e. either Pixel::BGRA8888 or Pixel::L8).
60  */
61 void TypesetGlyph(GlyphData&           data,
62                   const Vector2* const position,
63                   const Vector4* const color,
64                   Typesetter::Style    style,
65                   Pixel::Format        pixelFormat)
66 {
67   if((0u == data.glyphBitmap.width) || (0u == data.glyphBitmap.height))
68   {
69     // Nothing to do if the width or height of the buffer is zero.
70     return;
71   }
72
73   const int widthMinusOne  = static_cast<int>(data.width - 1u);
74   const int heightMinusOne = static_cast<int>(data.height - 1u);
75
76   if(Pixel::RGBA8888 == pixelFormat)
77   {
78     // Whether the given glyph is a color one.
79     const bool     isColorGlyph   = data.glyphBitmap.isColorEmoji || data.glyphBitmap.isColorBitmap;
80     const uint32_t glyphPixelSize = Pixel::GetBytesPerPixel(data.glyphBitmap.format);
81     const uint32_t alphaIndex     = glyphPixelSize - 1u;
82     const bool     swapChannelsBR = Pixel::BGRA8888 == data.glyphBitmap.format;
83
84     // Pointer to the color glyph if there is one.
85     const uint32_t* const colorGlyphBuffer = isColorGlyph ? reinterpret_cast<uint32_t*>(data.glyphBitmap.buffer) : NULL;
86
87     // Initial vertical offset.
88     const int yOffset = data.verticalOffset + position->y;
89
90     uint32_t* bitmapBuffer = reinterpret_cast<uint32_t*>(data.bitmapBuffer.GetBuffer());
91
92     // Traverse the pixels of the glyph line per line.
93     for(int lineIndex = 0, glyphHeight = static_cast<int>(data.glyphBitmap.height); lineIndex < glyphHeight; ++lineIndex)
94     {
95       const int yOffsetIndex = yOffset + lineIndex;
96       if((0 > yOffsetIndex) || (yOffsetIndex > heightMinusOne))
97       {
98         // Do not write out of bounds.
99         continue;
100       }
101
102       const int verticalOffset    = yOffsetIndex * data.width;
103       const int xOffset           = data.horizontalOffset + position->x;
104       const int glyphBufferOffset = lineIndex * static_cast<int>(data.glyphBitmap.width);
105       for(int index = 0, glyphWidth = static_cast<int>(data.glyphBitmap.width); index < glyphWidth; ++index)
106       {
107         const int xOffsetIndex = xOffset + index;
108         if((0 > xOffsetIndex) || (xOffsetIndex > widthMinusOne))
109         {
110           // Don't write out of bounds.
111           continue;
112         }
113
114         if(isColorGlyph)
115         {
116           // Retrieves the color from the color glyph.
117           uint32_t packedColorGlyph       = *(colorGlyphBuffer + glyphBufferOffset + index);
118           uint8_t* packedColorGlyphBuffer = reinterpret_cast<uint8_t*>(&packedColorGlyph);
119
120           // Update the alpha channel.
121           if(Typesetter::STYLE_MASK == style || Typesetter::STYLE_OUTLINE == style) // Outline not shown for color glyph
122           {
123             // Create an alpha mask for color glyph.
124             *(packedColorGlyphBuffer + 3u) = 0u;
125             *(packedColorGlyphBuffer + 2u) = 0u;
126             *(packedColorGlyphBuffer + 1u) = 0u;
127             *packedColorGlyphBuffer        = 0u;
128           }
129           else
130           {
131             const uint8_t colorAlpha       = static_cast<uint8_t>(color->a * static_cast<float>(*(packedColorGlyphBuffer + 3u)));
132             *(packedColorGlyphBuffer + 3u) = colorAlpha;
133
134             if(Typesetter::STYLE_SHADOW == style)
135             {
136               // The shadow of color glyph needs to have the shadow color.
137               *(packedColorGlyphBuffer + 2u) = static_cast<uint8_t>(color->b * colorAlpha);
138               *(packedColorGlyphBuffer + 1u) = static_cast<uint8_t>(color->g * colorAlpha);
139               *packedColorGlyphBuffer        = static_cast<uint8_t>(color->r * colorAlpha);
140             }
141             else
142             {
143               if(swapChannelsBR)
144               {
145                 std::swap(*packedColorGlyphBuffer, *(packedColorGlyphBuffer + 2u)); // Swap B and R.
146               }
147
148               *(packedColorGlyphBuffer + 2u) = (*(packedColorGlyphBuffer + 2u) * colorAlpha / 255);
149               *(packedColorGlyphBuffer + 1u) = (*(packedColorGlyphBuffer + 1u) * colorAlpha / 255);
150               *packedColorGlyphBuffer        = (*(packedColorGlyphBuffer)*colorAlpha / 255);
151
152               if(data.glyphBitmap.isColorBitmap)
153               {
154                 *(packedColorGlyphBuffer + 2u) = static_cast<uint8_t>(*(packedColorGlyphBuffer + 2u) * color->b);
155                 *(packedColorGlyphBuffer + 1u) = static_cast<uint8_t>(*(packedColorGlyphBuffer + 1u) * color->g);
156                 *packedColorGlyphBuffer        = static_cast<uint8_t>(*packedColorGlyphBuffer * color->r);
157               }
158             }
159           }
160
161           // Set the color into the final pixel buffer.
162           *(bitmapBuffer + verticalOffset + xOffsetIndex) = packedColorGlyph;
163         }
164         else
165         {
166           // Pack the given color into a 32bit buffer. The alpha channel will be updated later for each pixel.
167           // The format is RGBA8888.
168           uint32_t packedColor       = 0u;
169           uint8_t* packedColorBuffer = reinterpret_cast<uint8_t*>(&packedColor);
170
171           // Update the alpha channel.
172           const uint8_t alpha = *(data.glyphBitmap.buffer + glyphPixelSize * (glyphBufferOffset + index) + alphaIndex);
173
174           // Copy non-transparent pixels only
175           if(alpha > 0u)
176           {
177             // Check alpha of overlapped pixels
178             uint32_t& currentColor             = *(bitmapBuffer + verticalOffset + xOffsetIndex);
179             uint8_t*  packedCurrentColorBuffer = reinterpret_cast<uint8_t*>(&currentColor);
180
181             // For any pixel overlapped with the pixel in previous glyphs, make sure we don't
182             // overwrite a previous bigger alpha with a smaller alpha (in order to avoid
183             // semi-transparent gaps between joint glyphs with overlapped pixels, which could
184             // happen, for example, in the RTL text when we copy glyphs from right to left).
185             uint8_t currentAlpha = *(packedCurrentColorBuffer + 3u);
186             currentAlpha         = std::max(currentAlpha, alpha);
187
188             // Color is pre-muliplied with its alpha.
189             *(packedColorBuffer + 3u) = static_cast<uint8_t>(color->a * currentAlpha);
190             *(packedColorBuffer + 2u) = static_cast<uint8_t>(color->b * currentAlpha);
191             *(packedColorBuffer + 1u) = static_cast<uint8_t>(color->g * currentAlpha);
192             *(packedColorBuffer)      = static_cast<uint8_t>(color->r * currentAlpha);
193
194             // Set the color into the final pixel buffer.
195             currentColor = packedColor;
196           }
197         }
198       }
199     }
200   }
201   else
202   {
203     // Whether the given glyph is a color one.
204     const bool     isColorGlyph   = data.glyphBitmap.isColorEmoji || data.glyphBitmap.isColorBitmap;
205     const uint32_t glyphPixelSize = Pixel::GetBytesPerPixel(data.glyphBitmap.format);
206     const uint32_t alphaIndex     = glyphPixelSize - 1u;
207
208     // Initial vertical offset.
209     const int yOffset = data.verticalOffset + position->y;
210
211     uint8_t* bitmapBuffer = reinterpret_cast<uint8_t*>(data.bitmapBuffer.GetBuffer());
212
213     // Traverse the pixels of the glyph line per line.
214     for(int lineIndex = 0, glyphHeight = static_cast<int>(data.glyphBitmap.height); lineIndex < glyphHeight; ++lineIndex)
215     {
216       const int yOffsetIndex = yOffset + lineIndex;
217       if((0 > yOffsetIndex) || (yOffsetIndex > heightMinusOne))
218       {
219         // Do not write out of bounds.
220         continue;
221       }
222
223       const int verticalOffset    = yOffsetIndex * data.width;
224       const int xOffset           = data.horizontalOffset + position->x;
225       const int glyphBufferOffset = lineIndex * static_cast<int>(data.glyphBitmap.width);
226       for(int index = 0, glyphWidth = static_cast<int>(data.glyphBitmap.width); index < glyphWidth; ++index)
227       {
228         const int xOffsetIndex = xOffset + index;
229         if((0 > xOffsetIndex) || (xOffsetIndex > widthMinusOne))
230         {
231           // Don't write out of bounds.
232           continue;
233         }
234
235         if(!isColorGlyph)
236         {
237           // Update the alpha channel.
238           const uint8_t alpha = *(data.glyphBitmap.buffer + glyphPixelSize * (glyphBufferOffset + index) + alphaIndex);
239
240           // Copy non-transparent pixels only
241           if(alpha > 0u)
242           {
243             // Check alpha of overlapped pixels
244             uint8_t& currentAlpha = *(bitmapBuffer + verticalOffset + xOffsetIndex);
245
246             // For any pixel overlapped with the pixel in previous glyphs, make sure we don't
247             // overwrite a previous bigger alpha with a smaller alpha (in order to avoid
248             // semi-transparent gaps between joint glyphs with overlapped pixels, which could
249             // happen, for example, in the RTL text when we copy glyphs from right to left).
250             currentAlpha = std::max(currentAlpha, alpha);
251           }
252         }
253       }
254     }
255   }
256 }
257
258 bool IsGlyphUnderlined(GlyphIndex              index,
259                        const Vector<GlyphRun>& underlineRuns)
260 {
261   for(Vector<GlyphRun>::ConstIterator it    = underlineRuns.Begin(),
262                                       endIt = underlineRuns.End();
263       it != endIt;
264       ++it)
265   {
266     const GlyphRun& run = *it;
267
268     if((run.glyphIndex <= index) && (index < run.glyphIndex + run.numberOfGlyphs))
269     {
270       return true;
271     }
272   }
273
274   return false;
275 }
276
277 /// Helper method to fetch the underline metrics for the specified font glyph
278 void FetchFontUnderlineMetrics(
279   TextAbstraction::FontClient& fontClient,
280   const GlyphInfo* const       glyphInfo,
281   float&                       currentUnderlinePosition,
282   const float                  underlineHeight,
283   float&                       currentUnderlineThickness,
284   float&                       maxUnderlineThickness,
285   FontId&                      lastUnderlinedFontId)
286 {
287   FontMetrics fontMetrics;
288   fontClient.GetFontMetrics(glyphInfo->fontId, fontMetrics);
289   currentUnderlinePosition = ceil(fabsf(fontMetrics.underlinePosition));
290   const float descender    = ceil(fabsf(fontMetrics.descender));
291
292   if(fabsf(underlineHeight) < Math::MACHINE_EPSILON_1000)
293   {
294     currentUnderlineThickness = fontMetrics.underlineThickness;
295
296     // Ensure underline will be at least a pixel high
297     if(currentUnderlineThickness < 1.0f)
298     {
299       currentUnderlineThickness = 1.0f;
300     }
301     else
302     {
303       currentUnderlineThickness = ceil(currentUnderlineThickness);
304     }
305   }
306
307   // The underline thickness should be the max underline thickness of all glyphs of the line.
308   if(currentUnderlineThickness > maxUnderlineThickness)
309   {
310     maxUnderlineThickness = currentUnderlineThickness;
311   }
312
313   // Clamp the underline position at the font descender and check for ( as EFL describes it ) a broken font
314   if(currentUnderlinePosition > descender)
315   {
316     currentUnderlinePosition = descender;
317   }
318
319   if(fabsf(currentUnderlinePosition) < Math::MACHINE_EPSILON_1000)
320   {
321     // Move offset down by one ( EFL behavior )
322     currentUnderlinePosition = 1.0f;
323   }
324
325   lastUnderlinedFontId = glyphInfo->fontId;
326 }
327
328 /// Draws the specified color to the pixel buffer
329 void WriteColorToPixelBuffer(
330   GlyphData&         glyphData,
331   uint32_t*          bitmapBuffer,
332   const Vector4&     color,
333   const unsigned int x,
334   const unsigned int y)
335 {
336   // Always RGBA image for text with styles
337   uint32_t pixel       = *(bitmapBuffer + y * glyphData.width + x);
338   uint8_t* pixelBuffer = reinterpret_cast<uint8_t*>(&pixel);
339
340   // Write the color to the pixel buffer
341   uint8_t colorAlpha  = static_cast<uint8_t>(color.a * 255.f);
342   *(pixelBuffer + 3u) = colorAlpha;
343   *(pixelBuffer + 2u) = static_cast<uint8_t>(color.b * colorAlpha);
344   *(pixelBuffer + 1u) = static_cast<uint8_t>(color.g * colorAlpha);
345   *(pixelBuffer)      = static_cast<uint8_t>(color.r * colorAlpha);
346
347   *(bitmapBuffer + y * glyphData.width + x) = pixel;
348 }
349
350 /// Draws the specified underline color to the buffer
351 void DrawUnderline(
352   const Vector4&     underlineColor,
353   const unsigned int bufferWidth,
354   const unsigned int bufferHeight,
355   GlyphData&         glyphData,
356   const float        baseline,
357   const float        currentUnderlinePosition,
358   const float        maxUnderlineThickness,
359   const float        lineExtentLeft,
360   const float        lineExtentRight)
361 {
362   int       underlineYOffset = glyphData.verticalOffset + baseline + currentUnderlinePosition;
363   uint32_t* bitmapBuffer     = reinterpret_cast<uint32_t*>(glyphData.bitmapBuffer.GetBuffer());
364
365   for(unsigned int y = underlineYOffset; y < underlineYOffset + maxUnderlineThickness; y++)
366   {
367     if(y > bufferHeight - 1)
368     {
369       // Do not write out of bounds.
370       break;
371     }
372
373     for(unsigned int x = glyphData.horizontalOffset + lineExtentLeft; x <= glyphData.horizontalOffset + lineExtentRight; x++)
374     {
375       if(x > bufferWidth - 1)
376       {
377         // Do not write out of bounds.
378         break;
379       }
380
381       WriteColorToPixelBuffer(glyphData, bitmapBuffer, underlineColor, x, y);
382     }
383   }
384 }
385
386 /// Draws the background color to the buffer
387 void DrawBackgroundColor(
388   Vector4            backgroundColor,
389   const unsigned int bufferWidth,
390   const unsigned int bufferHeight,
391   GlyphData&         glyphData,
392   const float        baseline,
393   const LineRun&     line,
394   const float        lineExtentLeft,
395   const float        lineExtentRight)
396 {
397   uint32_t* bitmapBuffer = reinterpret_cast<uint32_t*>(glyphData.bitmapBuffer.GetBuffer());
398
399   for(int y = glyphData.verticalOffset + baseline - line.ascender; y < glyphData.verticalOffset + baseline - line.descender; y++)
400   {
401     if((y < 0) || (y > static_cast<int>(bufferHeight - 1)))
402     {
403       // Do not write out of bounds.
404       continue;
405     }
406
407     for(int x = glyphData.horizontalOffset + lineExtentLeft; x <= glyphData.horizontalOffset + lineExtentRight; x++)
408     {
409       if((x < 0) || (x > static_cast<int>(bufferWidth - 1)))
410       {
411         // Do not write out of bounds.
412         continue;
413       }
414
415       WriteColorToPixelBuffer(glyphData, bitmapBuffer, backgroundColor, x, y);
416     }
417   }
418 }
419
420 Devel::PixelBuffer DrawGlyphsBackground(const ViewModel* model, Devel::PixelBuffer& buffer, const unsigned int bufferWidth, const unsigned int bufferHeight, bool ignoreHorizontalAlignment, int horizontalOffset, int verticalOffset)
421 {
422   // Retrieve lines, glyphs, positions and colors from the view model.
423   const Length            modelNumberOfLines           = model->GetNumberOfLines();
424   const LineRun* const    modelLinesBuffer             = model->GetLines();
425   const Length            numberOfGlyphs               = model->GetNumberOfGlyphs();
426   const GlyphInfo* const  glyphsBuffer                 = model->GetGlyphs();
427   const Vector2* const    positionBuffer               = model->GetLayout();
428   const Vector4* const    backgroundColorsBuffer       = model->GetBackgroundColors();
429   const ColorIndex* const backgroundColorIndicesBuffer = model->GetBackgroundColorIndices();
430
431   // Create and initialize the pixel buffer.
432   GlyphData glyphData;
433   glyphData.verticalOffset   = verticalOffset;
434   glyphData.width            = bufferWidth;
435   glyphData.height           = bufferHeight;
436   glyphData.bitmapBuffer     = buffer;
437   glyphData.horizontalOffset = 0;
438
439   ColorIndex prevBackgroundColorIndex = 0;
440   ColorIndex backgroundColorIndex     = 0;
441
442   // Traverses the lines of the text.
443   for(LineIndex lineIndex = 0u; lineIndex < modelNumberOfLines; ++lineIndex)
444   {
445     const LineRun& line = *(modelLinesBuffer + lineIndex);
446
447     // Sets the horizontal offset of the line.
448     glyphData.horizontalOffset = ignoreHorizontalAlignment ? 0 : static_cast<int>(line.alignmentOffset);
449     glyphData.horizontalOffset += horizontalOffset;
450
451     // Increases the vertical offset with the line's ascender.
452     glyphData.verticalOffset += static_cast<int>(line.ascender);
453
454     // Include line spacing after first line
455     if(lineIndex > 0u)
456     {
457       glyphData.verticalOffset += static_cast<int>(line.lineSpacing);
458     }
459
460     float left     = bufferWidth;
461     float right    = 0.0f;
462     float baseline = 0.0f;
463
464     // Traverses the glyphs of the line.
465     const GlyphIndex endGlyphIndex = std::min(numberOfGlyphs, line.glyphRun.glyphIndex + line.glyphRun.numberOfGlyphs);
466     for(GlyphIndex glyphIndex = line.glyphRun.glyphIndex; glyphIndex < endGlyphIndex; ++glyphIndex)
467     {
468       // Retrieve the glyph's info.
469       const GlyphInfo* const glyphInfo = glyphsBuffer + glyphIndex;
470
471       if((glyphInfo->width < Math::MACHINE_EPSILON_1000) ||
472          (glyphInfo->height < Math::MACHINE_EPSILON_1000))
473       {
474         // Nothing to do if default background color, the glyph's width or height is zero.
475         continue;
476       }
477
478       backgroundColorIndex = (nullptr == backgroundColorsBuffer) ? 0u : *(backgroundColorIndicesBuffer + glyphIndex);
479
480       if((backgroundColorIndex != prevBackgroundColorIndex) &&
481          (prevBackgroundColorIndex != 0u))
482       {
483         const Vector4& backgroundColor = *(backgroundColorsBuffer + prevBackgroundColorIndex - 1u);
484         DrawBackgroundColor(backgroundColor, bufferWidth, bufferHeight, glyphData, baseline, line, left, right);
485       }
486
487       if(backgroundColorIndex == 0u)
488       {
489         prevBackgroundColorIndex = backgroundColorIndex;
490         //if background color is the default do nothing
491         continue;
492       }
493
494       // Retrieves the glyph's position.
495       const Vector2* const position = positionBuffer + glyphIndex;
496
497       if(baseline < position->y + glyphInfo->yBearing)
498       {
499         baseline = position->y + glyphInfo->yBearing;
500       }
501
502       // Calculate the positions of leftmost and rightmost glyphs in the current line
503       if((position->x < left) || (backgroundColorIndex != prevBackgroundColorIndex))
504       {
505         left = position->x - glyphInfo->xBearing;
506       }
507
508       if(position->x + glyphInfo->width > right)
509       {
510         right = position->x - glyphInfo->xBearing + glyphInfo->advance;
511       }
512
513       prevBackgroundColorIndex = backgroundColorIndex;
514     }
515
516     //draw last background at line end if not default
517     if(backgroundColorIndex != 0u)
518     {
519       const Vector4& backgroundColor = *(backgroundColorsBuffer + backgroundColorIndex - 1u);
520       DrawBackgroundColor(backgroundColor, bufferWidth, bufferHeight, glyphData, baseline, line, left, right);
521     }
522
523     // Increases the vertical offset with the line's descender.
524     glyphData.verticalOffset += static_cast<int>(-line.descender);
525   }
526
527   return glyphData.bitmapBuffer;
528 }
529
530 } // namespace
531
532 TypesetterPtr Typesetter::New(const ModelInterface* const model)
533 {
534   return TypesetterPtr(new Typesetter(model));
535 }
536
537 ViewModel* Typesetter::GetViewModel()
538 {
539   return mModel;
540 }
541
542 Devel::PixelBuffer Typesetter::CreateImageBuffer(const unsigned int bufferWidth, const unsigned int bufferHeight, Pixel::Format pixelFormat)
543 {
544   Devel::PixelBuffer imageBuffer = Devel::PixelBuffer::New(bufferWidth, bufferHeight, pixelFormat);
545
546   if(Pixel::RGBA8888 == pixelFormat)
547   {
548     const unsigned int bufferSizeInt  = bufferWidth * bufferHeight;
549     const unsigned int bufferSizeChar = 4u * bufferSizeInt;
550     memset(imageBuffer.GetBuffer(), 0u, bufferSizeChar);
551   }
552   else
553   {
554     memset(imageBuffer.GetBuffer(), 0, bufferWidth * bufferHeight);
555   }
556
557   return imageBuffer;
558 }
559
560 PixelData Typesetter::Render(const Vector2& size, Toolkit::DevelText::TextDirection::Type textDirection, RenderBehaviour behaviour, bool ignoreHorizontalAlignment, Pixel::Format pixelFormat)
561 {
562   // @todo. This initial implementation for a TextLabel has only one visible page.
563
564   // Elides the text if needed.
565   mModel->ElideGlyphs();
566
567   // Retrieves the layout size.
568   const Size& layoutSize = mModel->GetLayoutSize();
569
570   const int outlineWidth = static_cast<int>(mModel->GetOutlineWidth());
571
572   // Set the offset for the horizontal alignment according to the text direction and outline width.
573   int penX = 0;
574
575   switch(mModel->GetHorizontalAlignment())
576   {
577     case HorizontalAlignment::BEGIN:
578     {
579       // No offset to add.
580       break;
581     }
582     case HorizontalAlignment::CENTER:
583     {
584       penX += (textDirection == Toolkit::DevelText::TextDirection::LEFT_TO_RIGHT) ? -outlineWidth : outlineWidth;
585       break;
586     }
587     case HorizontalAlignment::END:
588     {
589       penX += (textDirection == Toolkit::DevelText::TextDirection::LEFT_TO_RIGHT) ? -outlineWidth * 2 : outlineWidth * 2;
590       break;
591     }
592   }
593
594   // Set the offset for the vertical alignment.
595   int penY = 0u;
596
597   switch(mModel->GetVerticalAlignment())
598   {
599     case VerticalAlignment::TOP:
600     {
601       // No offset to add.
602       break;
603     }
604     case VerticalAlignment::CENTER:
605     {
606       penY = static_cast<int>(0.5f * (size.height - layoutSize.height));
607       penY = penY < 0.f ? 0.f : penY;
608       break;
609     }
610     case VerticalAlignment::BOTTOM:
611     {
612       penY = static_cast<int>(size.height - layoutSize.height);
613       break;
614     }
615   }
616
617   // Calculate vertical line alignment
618   switch(mModel->GetVerticalLineAlignment())
619   {
620     case DevelText::VerticalLineAlignment::TOP:
621     {
622       break;
623     }
624     case DevelText::VerticalLineAlignment::MIDDLE:
625     {
626       const auto& line = *mModel->GetLines();
627       penY -= line.descender;
628       penY += static_cast<int>(line.lineSpacing * 0.5f + line.descender);
629       break;
630     }
631     case DevelText::VerticalLineAlignment::BOTTOM:
632     {
633       const auto& line       = *mModel->GetLines();
634       const auto  lineHeight = line.ascender + (-line.descender) + line.lineSpacing;
635       penY += static_cast<int>(lineHeight - (line.ascender - line.descender));
636       break;
637     }
638   }
639
640   // Generate the image buffers of the text for each different style first,
641   // then combine all of them together as one final image buffer. We try to
642   // do all of these in CPU only, so that once the final texture is generated,
643   // no calculation is needed in GPU during each frame.
644
645   const unsigned int bufferWidth  = static_cast<unsigned int>(size.width);
646   const unsigned int bufferHeight = static_cast<unsigned int>(size.height);
647
648   const unsigned int bufferSizeInt  = bufferWidth * bufferHeight;
649   const unsigned int bufferSizeChar = 4u * bufferSizeInt;
650
651   Length numberOfGlyphs = mModel->GetNumberOfGlyphs();
652
653   Devel::PixelBuffer imageBuffer;
654
655   if(RENDER_MASK == behaviour)
656   {
657     // Generate the image buffer as an alpha mask for color glyphs.
658     imageBuffer = CreateImageBuffer(bufferWidth, bufferHeight, Typesetter::STYLE_MASK, ignoreHorizontalAlignment, pixelFormat, penX, penY, 0u, numberOfGlyphs - 1);
659   }
660   else if(RENDER_NO_TEXT == behaviour)
661   {
662     // Generate an empty image buffer so that it can been combined with the image buffers for styles
663     imageBuffer = Devel::PixelBuffer::New(bufferWidth, bufferHeight, Pixel::RGBA8888);
664     memset(imageBuffer.GetBuffer(), 0u, bufferSizeChar);
665   }
666   else
667   {
668     // Generate the image buffer for the text with no style.
669     imageBuffer = CreateImageBuffer(bufferWidth, bufferHeight, Typesetter::STYLE_NONE, ignoreHorizontalAlignment, pixelFormat, penX, penY, 0u, numberOfGlyphs - 1);
670   }
671
672   if((RENDER_NO_STYLES != behaviour) && (RENDER_MASK != behaviour))
673   {
674     // Generate the outline if enabled
675     const uint16_t outlineWidth = mModel->GetOutlineWidth();
676     if(outlineWidth != 0u)
677     {
678       // Create the image buffer for outline
679       Devel::PixelBuffer outlineImageBuffer = CreateImageBuffer(bufferWidth, bufferHeight, Typesetter::STYLE_OUTLINE, ignoreHorizontalAlignment, pixelFormat, penX, penY, 0u, numberOfGlyphs - 1);
680
681       // Combine the two buffers
682       imageBuffer = CombineImageBuffer(imageBuffer, outlineImageBuffer, bufferWidth, bufferHeight);
683     }
684
685     // @todo. Support shadow and underline for partial text later on.
686
687     // Generate the shadow if enabled
688     const Vector2& shadowOffset = mModel->GetShadowOffset();
689     if(fabsf(shadowOffset.x) > Math::MACHINE_EPSILON_1 || fabsf(shadowOffset.y) > Math::MACHINE_EPSILON_1)
690     {
691       // Create the image buffer for shadow
692       Devel::PixelBuffer shadowImageBuffer = CreateImageBuffer(bufferWidth, bufferHeight, Typesetter::STYLE_SHADOW, ignoreHorizontalAlignment, pixelFormat, penX, penY, 0u, numberOfGlyphs - 1);
693
694       // Check whether it will be a soft shadow
695       const float& blurRadius = mModel->GetShadowBlurRadius();
696
697       if(blurRadius > Math::MACHINE_EPSILON_1)
698       {
699         shadowImageBuffer.ApplyGaussianBlur(blurRadius);
700       }
701
702       // Combine the two buffers
703       imageBuffer = CombineImageBuffer(imageBuffer, shadowImageBuffer, bufferWidth, bufferHeight);
704     }
705
706     // Generate the underline if enabled
707     const bool underlineEnabled = mModel->IsUnderlineEnabled();
708     if(underlineEnabled)
709     {
710       // Create the image buffer for underline
711       Devel::PixelBuffer underlineImageBuffer = CreateImageBuffer(bufferWidth, bufferHeight, Typesetter::STYLE_UNDERLINE, ignoreHorizontalAlignment, pixelFormat, penX, penY, 0u, numberOfGlyphs - 1);
712
713       // Combine the two buffers
714       imageBuffer = CombineImageBuffer(imageBuffer, underlineImageBuffer, bufferWidth, bufferHeight);
715     }
716
717     // Generate the background if enabled
718     const bool backgroundEnabled   = mModel->IsBackgroundEnabled();
719     const bool backgroundMarkupSet = mModel->IsMarkupBackgroundColorSet();
720     if(backgroundEnabled || backgroundMarkupSet)
721     {
722       Devel::PixelBuffer backgroundImageBuffer;
723
724       if(backgroundEnabled)
725       {
726         backgroundImageBuffer = CreateImageBuffer(bufferWidth, bufferHeight, Typesetter::STYLE_BACKGROUND, ignoreHorizontalAlignment, pixelFormat, penX, penY, 0u, numberOfGlyphs - 1);
727       }
728       else
729       {
730         backgroundImageBuffer = CreateImageBuffer(bufferWidth, bufferHeight, pixelFormat);
731       }
732
733       if(backgroundMarkupSet)
734       {
735         DrawGlyphsBackground(mModel, backgroundImageBuffer, bufferWidth, bufferHeight, ignoreHorizontalAlignment, penX, penY);
736       }
737
738       // Combine the two buffers
739       imageBuffer = CombineImageBuffer(imageBuffer, backgroundImageBuffer, bufferWidth, bufferHeight);
740     }
741
742     // Markup-Processor
743
744     imageBuffer = ApplyMarkupProcessorOnPixelBuffer(imageBuffer, bufferWidth, bufferHeight, ignoreHorizontalAlignment, pixelFormat, penX, penY);
745   }
746
747   // Create the final PixelData for the combined image buffer
748   PixelData pixelData = Devel::PixelBuffer::Convert(imageBuffer);
749
750   return pixelData;
751 }
752
753 Devel::PixelBuffer Typesetter::CreateImageBuffer(const unsigned int bufferWidth, const unsigned int bufferHeight, Typesetter::Style style, bool ignoreHorizontalAlignment, Pixel::Format pixelFormat, int horizontalOffset, int verticalOffset, GlyphIndex fromGlyphIndex, GlyphIndex toGlyphIndex)
754 {
755   // Retrieve lines, glyphs, positions and colors from the view model.
756   const Length            modelNumberOfLines = mModel->GetNumberOfLines();
757   const LineRun* const    modelLinesBuffer   = mModel->GetLines();
758   const Length            numberOfGlyphs     = mModel->GetNumberOfGlyphs();
759   const GlyphInfo* const  glyphsBuffer       = mModel->GetGlyphs();
760   const Vector2* const    positionBuffer     = mModel->GetLayout();
761   const Vector4* const    colorsBuffer       = mModel->GetColors();
762   const ColorIndex* const colorIndexBuffer   = mModel->GetColorIndices();
763   const GlyphInfo*        hyphens            = mModel->GetHyphens();
764   const Length*           hyphenIndices      = mModel->GetHyphenIndices();
765   const Length            hyphensCount       = mModel->GetHyphensCount();
766
767   // Whether to use the default color.
768   const bool     useDefaultColor = (NULL == colorsBuffer);
769   const Vector4& defaultColor    = mModel->GetDefaultColor();
770
771   // Create and initialize the pixel buffer.
772   GlyphData glyphData;
773   glyphData.verticalOffset   = verticalOffset;
774   glyphData.width            = bufferWidth;
775   glyphData.height           = bufferHeight;
776   glyphData.bitmapBuffer     = CreateImageBuffer(bufferWidth, bufferHeight, pixelFormat);
777   glyphData.horizontalOffset = 0;
778
779   // Get a handle of the font client. Used to retrieve the bitmaps of the glyphs.
780   TextAbstraction::FontClient fontClient  = TextAbstraction::FontClient::Get();
781   Length                      hyphenIndex = 0;
782
783   // Traverses the lines of the text.
784   for(LineIndex lineIndex = 0u; lineIndex < modelNumberOfLines; ++lineIndex)
785   {
786     const LineRun& line = *(modelLinesBuffer + lineIndex);
787
788     // Sets the horizontal offset of the line.
789     glyphData.horizontalOffset = ignoreHorizontalAlignment ? 0 : static_cast<int>(line.alignmentOffset);
790     glyphData.horizontalOffset += horizontalOffset;
791
792     // Increases the vertical offset with the line's ascender.
793     glyphData.verticalOffset += static_cast<int>(line.ascender);
794
795     // Include line spacing after first line
796     if(lineIndex > 0u)
797     {
798       glyphData.verticalOffset += static_cast<int>(line.lineSpacing);
799     }
800
801     // Retrieves the glyph's outline width
802     float outlineWidth = static_cast<float>(mModel->GetOutlineWidth());
803
804     if(style == Typesetter::STYLE_OUTLINE)
805     {
806       glyphData.horizontalOffset -= outlineWidth;
807       if(lineIndex == 0u)
808       {
809         // Only need to add the vertical outline offset for the first line
810         glyphData.verticalOffset -= outlineWidth;
811       }
812     }
813     else if(style == Typesetter::STYLE_SHADOW)
814     {
815       const Vector2& shadowOffset = mModel->GetShadowOffset();
816       glyphData.horizontalOffset += shadowOffset.x - outlineWidth; // if outline enabled then shadow should offset from outline
817
818       if(lineIndex == 0u)
819       {
820         // Only need to add the vertical shadow offset for first line
821         glyphData.verticalOffset += shadowOffset.y - outlineWidth;
822       }
823     }
824
825     const bool     underlineEnabled = mModel->IsUnderlineEnabled();
826     const Vector4& underlineColor   = mModel->GetUnderlineColor();
827     const float    underlineHeight  = mModel->GetUnderlineHeight();
828
829     // Get the underline runs.
830     const Length     numberOfUnderlineRuns = mModel->GetNumberOfUnderlineRuns();
831     Vector<GlyphRun> underlineRuns;
832     underlineRuns.Resize(numberOfUnderlineRuns);
833     mModel->GetUnderlineRuns(underlineRuns.Begin(), 0u, numberOfUnderlineRuns);
834
835     bool thereAreUnderlinedGlyphs = false;
836
837     float currentUnderlinePosition  = 0.0f;
838     float currentUnderlineThickness = underlineHeight;
839     float maxUnderlineThickness     = currentUnderlineThickness;
840
841     FontId lastUnderlinedFontId = 0;
842
843     float lineExtentLeft  = bufferWidth;
844     float lineExtentRight = 0.0f;
845     float baseline        = 0.0f;
846     bool  addHyphen       = false;
847
848     // Traverses the glyphs of the line.
849     const GlyphIndex endGlyphIndex = std::min(numberOfGlyphs, line.glyphRun.glyphIndex + line.glyphRun.numberOfGlyphs);
850     for(GlyphIndex glyphIndex = line.glyphRun.glyphIndex; glyphIndex < endGlyphIndex; ++glyphIndex)
851     {
852       if(glyphIndex < fromGlyphIndex || glyphIndex > toGlyphIndex)
853       {
854         // Ignore any glyph that out of the specified range
855         continue;
856       }
857
858       // Retrieve the glyph's info.
859       const GlyphInfo* glyphInfo;
860
861       if(addHyphen && hyphens)
862       {
863         glyphInfo = hyphens + hyphenIndex;
864         hyphenIndex++;
865       }
866       else
867       {
868         glyphInfo = glyphsBuffer + glyphIndex;
869       }
870
871       if((glyphInfo->width < Math::MACHINE_EPSILON_1000) ||
872          (glyphInfo->height < Math::MACHINE_EPSILON_1000))
873       {
874         // Nothing to do if the glyph's width or height is zero.
875         continue;
876       }
877
878       const bool underlineGlyph = underlineEnabled || IsGlyphUnderlined(glyphIndex, underlineRuns);
879       thereAreUnderlinedGlyphs  = thereAreUnderlinedGlyphs || underlineGlyph;
880
881       // Are we still using the same fontId as previous
882       if(underlineGlyph && (glyphInfo->fontId != lastUnderlinedFontId))
883       {
884         // We need to fetch fresh font underline metrics
885         FetchFontUnderlineMetrics(fontClient, glyphInfo, currentUnderlinePosition, underlineHeight, currentUnderlineThickness, maxUnderlineThickness, lastUnderlinedFontId);
886       } // underline
887
888       // Retrieves the glyph's position.
889       Vector2 position = *(positionBuffer + glyphIndex);
890
891       if(addHyphen)
892       {
893         GlyphInfo tempInfo = *(glyphsBuffer + glyphIndex);
894         position.x         = position.x + tempInfo.advance - tempInfo.xBearing + glyphInfo->xBearing;
895         position.y         = -glyphInfo->yBearing;
896       }
897
898       if(baseline < position.y + glyphInfo->yBearing)
899       {
900         baseline = position.y + glyphInfo->yBearing;
901       }
902
903       // Calculate the positions of leftmost and rightmost glyphs in the current line
904       if(position.x < lineExtentLeft)
905       {
906         lineExtentLeft = position.x;
907       }
908
909       if(position.x + glyphInfo->width > lineExtentRight)
910       {
911         lineExtentRight = position.x + glyphInfo->width;
912       }
913
914       // Retrieves the glyph's color.
915       const ColorIndex colorIndex = useDefaultColor ? 0u : *(colorIndexBuffer + glyphIndex);
916
917       Vector4 color;
918       if(style == Typesetter::STYLE_SHADOW)
919       {
920         color = mModel->GetShadowColor();
921       }
922       else if(style == Typesetter::STYLE_OUTLINE)
923       {
924         color = mModel->GetOutlineColor();
925       }
926       else
927       {
928         color = (useDefaultColor || (0u == colorIndex)) ? defaultColor : *(colorsBuffer + (colorIndex - 1u));
929       }
930
931       // Premultiply alpha
932       color.r *= color.a;
933       color.g *= color.a;
934       color.b *= color.a;
935
936       // Retrieves the glyph's bitmap.
937       glyphData.glyphBitmap.buffer = NULL;
938       glyphData.glyphBitmap.width  = glyphInfo->width; // Desired width and height.
939       glyphData.glyphBitmap.height = glyphInfo->height;
940
941       if(style != Typesetter::STYLE_OUTLINE && style != Typesetter::STYLE_SHADOW)
942       {
943         // Don't render outline for other styles
944         outlineWidth = 0.0f;
945       }
946
947       if(style != Typesetter::STYLE_UNDERLINE)
948       {
949         fontClient.CreateBitmap(glyphInfo->fontId,
950                                 glyphInfo->index,
951                                 glyphInfo->isItalicRequired,
952                                 glyphInfo->isBoldRequired,
953                                 glyphData.glyphBitmap,
954                                 static_cast<int>(outlineWidth));
955       }
956
957       // Sets the glyph's bitmap into the bitmap of the whole text.
958       if(NULL != glyphData.glyphBitmap.buffer)
959       {
960         if(style == Typesetter::STYLE_OUTLINE)
961         {
962           // Set the position offset for the current glyph
963           glyphData.horizontalOffset -= glyphData.glyphBitmap.outlineOffsetX;
964           glyphData.verticalOffset -= glyphData.glyphBitmap.outlineOffsetY;
965         }
966
967         // Set the buffer of the glyph's bitmap into the final bitmap's buffer
968         TypesetGlyph(glyphData,
969                      &position,
970                      &color,
971                      style,
972                      pixelFormat);
973
974         if(style == Typesetter::STYLE_OUTLINE)
975         {
976           // Reset the position offset for the next glyph
977           glyphData.horizontalOffset += glyphData.glyphBitmap.outlineOffsetX;
978           glyphData.verticalOffset += glyphData.glyphBitmap.outlineOffsetY;
979         }
980
981         // delete the glyphBitmap.buffer as it is now copied into glyphData.bitmapBuffer
982         delete[] glyphData.glyphBitmap.buffer;
983         glyphData.glyphBitmap.buffer = NULL;
984       }
985
986       if(hyphenIndices)
987       {
988         while((hyphenIndex < hyphensCount) && (glyphIndex > hyphenIndices[hyphenIndex]))
989         {
990           hyphenIndex++;
991         }
992
993         addHyphen = ((hyphenIndex < hyphensCount) && ((glyphIndex + 1) == hyphenIndices[hyphenIndex]));
994         if(addHyphen)
995         {
996           glyphIndex--;
997         }
998       }
999     }
1000
1001     // Draw the underline from the leftmost glyph to the rightmost glyph
1002     if(thereAreUnderlinedGlyphs && style == Typesetter::STYLE_UNDERLINE)
1003     {
1004       DrawUnderline(underlineColor, bufferWidth, bufferHeight, glyphData, baseline, currentUnderlinePosition, maxUnderlineThickness, lineExtentLeft, lineExtentRight);
1005     }
1006
1007     // Draw the background color from the leftmost glyph to the rightmost glyph
1008     if(style == Typesetter::STYLE_BACKGROUND)
1009     {
1010       DrawBackgroundColor(mModel->GetBackgroundColor(), bufferWidth, bufferHeight, glyphData, baseline, line, lineExtentLeft, lineExtentRight);
1011     }
1012
1013     // Increases the vertical offset with the line's descender.
1014     glyphData.verticalOffset += static_cast<int>(-line.descender);
1015   }
1016
1017   return glyphData.bitmapBuffer;
1018 }
1019
1020 Devel::PixelBuffer Typesetter::CombineImageBuffer(Devel::PixelBuffer topPixelBuffer, Devel::PixelBuffer bottomPixelBuffer, const unsigned int bufferWidth, const unsigned int bufferHeight)
1021 {
1022   unsigned char* topBuffer    = topPixelBuffer.GetBuffer();
1023   unsigned char* bottomBuffer = bottomPixelBuffer.GetBuffer();
1024
1025   Devel::PixelBuffer combinedPixelBuffer;
1026
1027   if(topBuffer == NULL && bottomBuffer == NULL)
1028   {
1029     // Nothing to do if both buffers are empty.
1030     return combinedPixelBuffer;
1031   }
1032
1033   if(topBuffer == NULL)
1034   {
1035     // Nothing to do if topBuffer is empty.
1036     return bottomPixelBuffer;
1037   }
1038
1039   if(bottomBuffer == NULL)
1040   {
1041     // Nothing to do if bottomBuffer is empty.
1042     return topPixelBuffer;
1043   }
1044
1045   // Always combine two RGBA images
1046   const unsigned int bufferSizeInt  = bufferWidth * bufferHeight;
1047   const unsigned int bufferSizeChar = 4u * bufferSizeInt;
1048
1049   combinedPixelBuffer     = Devel::PixelBuffer::New(bufferWidth, bufferHeight, Pixel::RGBA8888);
1050   uint8_t* combinedBuffer = reinterpret_cast<uint8_t*>(combinedPixelBuffer.GetBuffer());
1051   memset(combinedBuffer, 0u, bufferSizeChar);
1052
1053   for(unsigned int pixelIndex = 0; pixelIndex < bufferSizeInt; pixelIndex++)
1054   {
1055     // If the alpha of the pixel in either buffer is not fully opaque, blend the two pixels.
1056     // Otherwise, copy pixel from topBuffer to combinedBuffer.
1057
1058     unsigned int alphaBuffer1 = topBuffer[pixelIndex * 4 + 3];
1059
1060     if(alphaBuffer1 != 255)
1061     {
1062       // At least one pixel is not fully opaque
1063       // "Over" blend the the pixel from topBuffer with the pixel in bottomBuffer
1064       combinedBuffer[pixelIndex * 4]     = topBuffer[pixelIndex * 4] + (bottomBuffer[pixelIndex * 4] * (255 - topBuffer[pixelIndex * 4 + 3]) / 255);
1065       combinedBuffer[pixelIndex * 4 + 1] = topBuffer[pixelIndex * 4 + 1] + (bottomBuffer[pixelIndex * 4 + 1] * (255 - topBuffer[pixelIndex * 4 + 3]) / 255);
1066       combinedBuffer[pixelIndex * 4 + 2] = topBuffer[pixelIndex * 4 + 2] + (bottomBuffer[pixelIndex * 4 + 2] * (255 - topBuffer[pixelIndex * 4 + 3]) / 255);
1067       combinedBuffer[pixelIndex * 4 + 3] = topBuffer[pixelIndex * 4 + 3] + (bottomBuffer[pixelIndex * 4 + 3] * (255 - topBuffer[pixelIndex * 4 + 3]) / 255);
1068     }
1069     else
1070     {
1071       // Copy the pixel from topBuffer to combinedBuffer
1072       combinedBuffer[pixelIndex * 4]     = topBuffer[pixelIndex * 4];
1073       combinedBuffer[pixelIndex * 4 + 1] = topBuffer[pixelIndex * 4 + 1];
1074       combinedBuffer[pixelIndex * 4 + 2] = topBuffer[pixelIndex * 4 + 2];
1075       combinedBuffer[pixelIndex * 4 + 3] = topBuffer[pixelIndex * 4 + 3];
1076     }
1077   }
1078
1079   return combinedPixelBuffer;
1080 }
1081
1082 Devel::PixelBuffer Typesetter::ApplyMarkupProcessorOnPixelBuffer(Devel::PixelBuffer topPixelBuffer, const unsigned int bufferWidth, const unsigned int bufferHeight, bool ignoreHorizontalAlignment, Pixel::Format pixelFormat, int horizontalOffset, int verticalOffset)
1083 {
1084   // Apply the markup-Processor if enabled
1085   const bool markupProcessorEnabled = mModel->IsMarkupProcessorEnabled();
1086   if(markupProcessorEnabled)
1087   {
1088     // Underline-tags (this is for Markup case)
1089     // Get the underline runs.
1090     const Length     numberOfUnderlineRuns = mModel->GetNumberOfUnderlineRuns();
1091     Vector<GlyphRun> underlineRuns;
1092     underlineRuns.Resize(numberOfUnderlineRuns);
1093     mModel->GetUnderlineRuns(underlineRuns.Begin(), 0u, numberOfUnderlineRuns);
1094
1095     // Iterate on the consecutive underlined glyph run and connect them into one chunk of underlined characters.
1096     Vector<GlyphRun>::ConstIterator itGlyphRun    = underlineRuns.Begin();
1097     Vector<GlyphRun>::ConstIterator endItGlyphRun = underlineRuns.End();
1098     GlyphIndex                      startGlyphIndex, endGlyphIndex;
1099
1100     //The outer loop to iterate on the separated chunks of underlined glyph runs
1101     while(itGlyphRun != endItGlyphRun)
1102     {
1103       startGlyphIndex = itGlyphRun->glyphIndex;
1104       endGlyphIndex   = startGlyphIndex;
1105       //The inner loop to make a connected underline for the consecutive characters
1106       do
1107       {
1108         endGlyphIndex += itGlyphRun->numberOfGlyphs;
1109         itGlyphRun++;
1110       } while(itGlyphRun != endItGlyphRun && itGlyphRun->glyphIndex == endGlyphIndex);
1111
1112       endGlyphIndex--;
1113
1114       // Create the image buffer for underline
1115       Devel::PixelBuffer underlineImageBuffer = CreateImageBuffer(bufferWidth, bufferHeight, Typesetter::STYLE_UNDERLINE, ignoreHorizontalAlignment, pixelFormat, horizontalOffset, verticalOffset, startGlyphIndex, endGlyphIndex);
1116       // Combine the two buffers
1117       topPixelBuffer = CombineImageBuffer(topPixelBuffer, underlineImageBuffer, bufferWidth, bufferHeight);
1118     }
1119   }
1120
1121   return topPixelBuffer;
1122 }
1123
1124 Typesetter::Typesetter(const ModelInterface* const model)
1125 : mModel(new ViewModel(model))
1126 {
1127 }
1128
1129 Typesetter::~Typesetter()
1130 {
1131   delete mModel;
1132 }
1133
1134 } // namespace Text
1135
1136 } // namespace Toolkit
1137
1138 } // namespace Dali