2 * Copyright (c) 2021 Samsung Electronics Co., Ltd.
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
19 #include <dali-toolkit/internal/text/rendering/text-typesetter.h>
22 #include <dali/devel-api/text-abstraction/font-client.h>
23 #include <dali/public-api/common/constants.h>
27 #include <dali-toolkit/devel-api/controls/text-controls/text-label-devel.h>
28 #include <dali-toolkit/internal/text/rendering/view-model.h>
38 const float HALF(0.5f);
40 * @brief Data struct used to set the buffer of the glyph's bitmap into the final bitmap's buffer.
44 Devel::PixelBuffer bitmapBuffer; ///< The buffer of the whole bitmap. The format is RGBA8888.
45 Vector2* position; ///< The position of the glyph.
46 TextAbstraction::FontClient::GlyphBufferData glyphBitmap; ///< The glyph's bitmap.
47 unsigned int width; ///< The bitmap's width.
48 unsigned int height; ///< The bitmap's height.
49 int horizontalOffset; ///< The horizontal offset to be added to the 'x' glyph's position.
50 int verticalOffset; ///< The vertical offset to be added to the 'y' glyph's position.
54 * @brief Sets the glyph's buffer into the bitmap's buffer.
56 * @param[in] data Struct which contains the glyph's data and the bitmap's data.
57 * @param[in] position The position of the glyph.
58 * @param[in] color The color of the glyph.
59 * @param[in] style The style of the text.
60 * @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).
62 void TypesetGlyph(GlyphData& data,
63 const Vector2* const position,
64 const Vector4* const color,
65 Typesetter::Style style,
66 Pixel::Format pixelFormat)
68 if((0u == data.glyphBitmap.width) || (0u == data.glyphBitmap.height))
70 // Nothing to do if the width or height of the buffer is zero.
74 const int widthMinusOne = static_cast<int>(data.width - 1u);
75 const int heightMinusOne = static_cast<int>(data.height - 1u);
77 if(Pixel::RGBA8888 == pixelFormat)
79 // Whether the given glyph is a color one.
80 const bool isColorGlyph = data.glyphBitmap.isColorEmoji || data.glyphBitmap.isColorBitmap;
81 const uint32_t glyphPixelSize = Pixel::GetBytesPerPixel(data.glyphBitmap.format);
82 const uint32_t alphaIndex = glyphPixelSize - 1u;
83 const bool swapChannelsBR = Pixel::BGRA8888 == data.glyphBitmap.format;
85 // Pointer to the color glyph if there is one.
86 const uint32_t* const colorGlyphBuffer = isColorGlyph ? reinterpret_cast<uint32_t*>(data.glyphBitmap.buffer) : NULL;
88 // Initial vertical offset.
89 const int yOffset = data.verticalOffset + position->y;
91 uint32_t* bitmapBuffer = reinterpret_cast<uint32_t*>(data.bitmapBuffer.GetBuffer());
93 // Traverse the pixels of the glyph line per line.
94 for(int lineIndex = 0, glyphHeight = static_cast<int>(data.glyphBitmap.height); lineIndex < glyphHeight; ++lineIndex)
96 const int yOffsetIndex = yOffset + lineIndex;
97 if((0 > yOffsetIndex) || (yOffsetIndex > heightMinusOne))
99 // Do not write out of bounds.
103 const int verticalOffset = yOffsetIndex * data.width;
104 const int xOffset = data.horizontalOffset + position->x;
105 const int glyphBufferOffset = lineIndex * static_cast<int>(data.glyphBitmap.width);
106 for(int index = 0, glyphWidth = static_cast<int>(data.glyphBitmap.width); index < glyphWidth; ++index)
108 const int xOffsetIndex = xOffset + index;
109 if((0 > xOffsetIndex) || (xOffsetIndex > widthMinusOne))
111 // Don't write out of bounds.
117 // Retrieves the color from the color glyph.
118 uint32_t packedColorGlyph = *(colorGlyphBuffer + glyphBufferOffset + index);
119 uint8_t* packedColorGlyphBuffer = reinterpret_cast<uint8_t*>(&packedColorGlyph);
121 // Update the alpha channel.
122 if(Typesetter::STYLE_MASK == style || Typesetter::STYLE_OUTLINE == style) // Outline not shown for color glyph
124 // Create an alpha mask for color glyph.
125 *(packedColorGlyphBuffer + 3u) = 0u;
126 *(packedColorGlyphBuffer + 2u) = 0u;
127 *(packedColorGlyphBuffer + 1u) = 0u;
128 *packedColorGlyphBuffer = 0u;
132 const uint8_t colorAlpha = static_cast<uint8_t>(color->a * static_cast<float>(*(packedColorGlyphBuffer + 3u)));
133 *(packedColorGlyphBuffer + 3u) = colorAlpha;
135 if(Typesetter::STYLE_SHADOW == style)
137 // The shadow of color glyph needs to have the shadow color.
138 *(packedColorGlyphBuffer + 2u) = static_cast<uint8_t>(color->b * colorAlpha);
139 *(packedColorGlyphBuffer + 1u) = static_cast<uint8_t>(color->g * colorAlpha);
140 *packedColorGlyphBuffer = static_cast<uint8_t>(color->r * colorAlpha);
146 std::swap(*packedColorGlyphBuffer, *(packedColorGlyphBuffer + 2u)); // Swap B and R.
149 *(packedColorGlyphBuffer + 2u) = (*(packedColorGlyphBuffer + 2u) * colorAlpha / 255);
150 *(packedColorGlyphBuffer + 1u) = (*(packedColorGlyphBuffer + 1u) * colorAlpha / 255);
151 *packedColorGlyphBuffer = (*(packedColorGlyphBuffer)*colorAlpha / 255);
153 if(data.glyphBitmap.isColorBitmap)
155 *(packedColorGlyphBuffer + 2u) = static_cast<uint8_t>(*(packedColorGlyphBuffer + 2u) * color->b);
156 *(packedColorGlyphBuffer + 1u) = static_cast<uint8_t>(*(packedColorGlyphBuffer + 1u) * color->g);
157 *packedColorGlyphBuffer = static_cast<uint8_t>(*packedColorGlyphBuffer * color->r);
162 // Set the color into the final pixel buffer.
163 *(bitmapBuffer + verticalOffset + xOffsetIndex) = packedColorGlyph;
167 // Pack the given color into a 32bit buffer. The alpha channel will be updated later for each pixel.
168 // The format is RGBA8888.
169 uint32_t packedColor = 0u;
170 uint8_t* packedColorBuffer = reinterpret_cast<uint8_t*>(&packedColor);
172 // Update the alpha channel.
173 const uint8_t alpha = *(data.glyphBitmap.buffer + glyphPixelSize * (glyphBufferOffset + index) + alphaIndex);
175 // Copy non-transparent pixels only
178 // Check alpha of overlapped pixels
179 uint32_t& currentColor = *(bitmapBuffer + verticalOffset + xOffsetIndex);
180 uint8_t* packedCurrentColorBuffer = reinterpret_cast<uint8_t*>(¤tColor);
182 // For any pixel overlapped with the pixel in previous glyphs, make sure we don't
183 // overwrite a previous bigger alpha with a smaller alpha (in order to avoid
184 // semi-transparent gaps between joint glyphs with overlapped pixels, which could
185 // happen, for example, in the RTL text when we copy glyphs from right to left).
186 uint8_t currentAlpha = *(packedCurrentColorBuffer + 3u);
187 currentAlpha = std::max(currentAlpha, alpha);
189 // Color is pre-muliplied with its alpha.
190 *(packedColorBuffer + 3u) = static_cast<uint8_t>(color->a * currentAlpha);
191 *(packedColorBuffer + 2u) = static_cast<uint8_t>(color->b * currentAlpha);
192 *(packedColorBuffer + 1u) = static_cast<uint8_t>(color->g * currentAlpha);
193 *(packedColorBuffer) = static_cast<uint8_t>(color->r * currentAlpha);
195 // Set the color into the final pixel buffer.
196 currentColor = packedColor;
204 // Whether the given glyph is a color one.
205 const bool isColorGlyph = data.glyphBitmap.isColorEmoji || data.glyphBitmap.isColorBitmap;
206 const uint32_t glyphPixelSize = Pixel::GetBytesPerPixel(data.glyphBitmap.format);
207 const uint32_t alphaIndex = glyphPixelSize - 1u;
209 // Initial vertical offset.
210 const int yOffset = data.verticalOffset + position->y;
212 uint8_t* bitmapBuffer = reinterpret_cast<uint8_t*>(data.bitmapBuffer.GetBuffer());
214 // Traverse the pixels of the glyph line per line.
215 for(int lineIndex = 0, glyphHeight = static_cast<int>(data.glyphBitmap.height); lineIndex < glyphHeight; ++lineIndex)
217 const int yOffsetIndex = yOffset + lineIndex;
218 if((0 > yOffsetIndex) || (yOffsetIndex > heightMinusOne))
220 // Do not write out of bounds.
224 const int verticalOffset = yOffsetIndex * data.width;
225 const int xOffset = data.horizontalOffset + position->x;
226 const int glyphBufferOffset = lineIndex * static_cast<int>(data.glyphBitmap.width);
227 for(int index = 0, glyphWidth = static_cast<int>(data.glyphBitmap.width); index < glyphWidth; ++index)
229 const int xOffsetIndex = xOffset + index;
230 if((0 > xOffsetIndex) || (xOffsetIndex > widthMinusOne))
232 // Don't write out of bounds.
238 // Update the alpha channel.
239 const uint8_t alpha = *(data.glyphBitmap.buffer + glyphPixelSize * (glyphBufferOffset + index) + alphaIndex);
241 // Copy non-transparent pixels only
244 // Check alpha of overlapped pixels
245 uint8_t& currentAlpha = *(bitmapBuffer + verticalOffset + xOffsetIndex);
247 // For any pixel overlapped with the pixel in previous glyphs, make sure we don't
248 // overwrite a previous bigger alpha with a smaller alpha (in order to avoid
249 // semi-transparent gaps between joint glyphs with overlapped pixels, which could
250 // happen, for example, in the RTL text when we copy glyphs from right to left).
251 currentAlpha = std::max(currentAlpha, alpha);
259 bool IsGlyphUnderlined(GlyphIndex index,
260 const Vector<GlyphRun>& underlineRuns)
262 for(Vector<GlyphRun>::ConstIterator it = underlineRuns.Begin(),
263 endIt = underlineRuns.End();
267 const GlyphRun& run = *it;
269 if((run.glyphIndex <= index) && (index < run.glyphIndex + run.numberOfGlyphs))
278 /// Helper method to fetch the underline metrics for the specified font glyph
279 void FetchFontDecorationlinesMetrics(
280 TextAbstraction::FontClient& fontClient,
281 const GlyphInfo* const glyphInfo,
282 float& currentUnderlinePosition,
283 const float underlineHeight,
284 float& currentUnderlineThickness,
285 float& maxUnderlineThickness,
286 FontId& lastlinedFontId,
287 const float strikethroughHeight,
288 float& currentStrikethroughThickness,
289 float& maxStrikethroughThickness)
291 FontMetrics fontMetrics;
292 fontClient.GetFontMetrics(glyphInfo->fontId, fontMetrics);
293 currentUnderlinePosition = ceil(fabsf(fontMetrics.underlinePosition));
294 const float descender = ceil(fabsf(fontMetrics.descender));
296 if(fabsf(underlineHeight) < Math::MACHINE_EPSILON_1000)
298 currentUnderlineThickness = fontMetrics.underlineThickness;
300 // Ensure underline will be at least a pixel high
301 if(currentUnderlineThickness < 1.0f)
303 currentUnderlineThickness = 1.0f;
307 currentUnderlineThickness = ceil(currentUnderlineThickness);
311 if(fabsf(strikethroughHeight) < Math::MACHINE_EPSILON_1000)
313 // Ensure strikethrough will be at least a pixel high
314 if(currentStrikethroughThickness < 1.0f)
316 currentStrikethroughThickness = 1.0f;
320 currentStrikethroughThickness = ceil(currentStrikethroughThickness);
324 // The underline thickness should be the max underline thickness of all glyphs of the line.
325 if(currentUnderlineThickness > maxUnderlineThickness)
327 maxUnderlineThickness = currentUnderlineThickness;
330 // The strikethrough thickness should be the max strikethrough thickness of all glyphs of the line.
331 if(currentStrikethroughThickness > maxStrikethroughThickness)
333 maxStrikethroughThickness = currentStrikethroughThickness;
336 // Clamp the underline position at the font descender and check for ( as EFL describes it ) a broken font
337 if(currentUnderlinePosition > descender)
339 currentUnderlinePosition = descender;
342 if(fabsf(currentUnderlinePosition) < Math::MACHINE_EPSILON_1000)
344 // Move offset down by one ( EFL behavior )
345 currentUnderlinePosition = 1.0f;
348 lastlinedFontId = glyphInfo->fontId;
351 /// Draws the specified color to the pixel buffer
352 void WriteColorToPixelBuffer(
353 GlyphData& glyphData,
354 uint32_t* bitmapBuffer,
355 const Vector4& color,
356 const unsigned int x,
357 const unsigned int y)
359 // Always RGBA image for text with styles
360 uint32_t pixel = *(bitmapBuffer + y * glyphData.width + x);
361 uint8_t* pixelBuffer = reinterpret_cast<uint8_t*>(&pixel);
363 // Write the color to the pixel buffer
364 uint8_t colorAlpha = static_cast<uint8_t>(color.a * 255.f);
365 *(pixelBuffer + 3u) = colorAlpha;
366 *(pixelBuffer + 2u) = static_cast<uint8_t>(color.b * colorAlpha);
367 *(pixelBuffer + 1u) = static_cast<uint8_t>(color.g * colorAlpha);
368 *(pixelBuffer) = static_cast<uint8_t>(color.r * colorAlpha);
370 *(bitmapBuffer + y * glyphData.width + x) = pixel;
373 /// Draws the specified underline color to the buffer
375 const Vector4& underlineColor,
376 const unsigned int bufferWidth,
377 const unsigned int bufferHeight,
378 GlyphData& glyphData,
379 const float baseline,
380 const float currentUnderlinePosition,
381 const float maxUnderlineThickness,
382 const float lineExtentLeft,
383 const float lineExtentRight)
385 int underlineYOffset = glyphData.verticalOffset + baseline + currentUnderlinePosition;
386 uint32_t* bitmapBuffer = reinterpret_cast<uint32_t*>(glyphData.bitmapBuffer.GetBuffer());
388 for(unsigned int y = underlineYOffset; y < underlineYOffset + maxUnderlineThickness; y++)
390 if(y > bufferHeight - 1)
392 // Do not write out of bounds.
396 for(unsigned int x = glyphData.horizontalOffset + lineExtentLeft; x <= glyphData.horizontalOffset + lineExtentRight; x++)
398 if(x > bufferWidth - 1)
400 // Do not write out of bounds.
404 WriteColorToPixelBuffer(glyphData, bitmapBuffer, underlineColor, x, y);
409 /// Draws the background color to the buffer
410 void DrawBackgroundColor(
411 Vector4 backgroundColor,
412 const unsigned int bufferWidth,
413 const unsigned int bufferHeight,
414 GlyphData& glyphData,
415 const float baseline,
417 const float lineExtentLeft,
418 const float lineExtentRight)
420 uint32_t* bitmapBuffer = reinterpret_cast<uint32_t*>(glyphData.bitmapBuffer.GetBuffer());
422 for(int y = glyphData.verticalOffset + baseline - line.ascender; y < glyphData.verticalOffset + baseline - line.descender; y++)
424 if((y < 0) || (y > static_cast<int>(bufferHeight - 1)))
426 // Do not write out of bounds.
430 for(int x = glyphData.horizontalOffset + lineExtentLeft; x <= glyphData.horizontalOffset + lineExtentRight; x++)
432 if((x < 0) || (x > static_cast<int>(bufferWidth - 1)))
434 // Do not write out of bounds.
438 WriteColorToPixelBuffer(glyphData, bitmapBuffer, backgroundColor, x, y);
443 Devel::PixelBuffer DrawGlyphsBackground(const ViewModel* model, Devel::PixelBuffer& buffer, const unsigned int bufferWidth, const unsigned int bufferHeight, bool ignoreHorizontalAlignment, int horizontalOffset, int verticalOffset)
445 // Retrieve lines, glyphs, positions and colors from the view model.
446 const Length modelNumberOfLines = model->GetNumberOfLines();
447 const LineRun* const modelLinesBuffer = model->GetLines();
448 const Length numberOfGlyphs = model->GetNumberOfGlyphs();
449 const GlyphInfo* const glyphsBuffer = model->GetGlyphs();
450 const Vector2* const positionBuffer = model->GetLayout();
451 const Vector4* const backgroundColorsBuffer = model->GetBackgroundColors();
452 const ColorIndex* const backgroundColorIndicesBuffer = model->GetBackgroundColorIndices();
454 // Create and initialize the pixel buffer.
456 glyphData.verticalOffset = verticalOffset;
457 glyphData.width = bufferWidth;
458 glyphData.height = bufferHeight;
459 glyphData.bitmapBuffer = buffer;
460 glyphData.horizontalOffset = 0;
462 ColorIndex prevBackgroundColorIndex = 0;
463 ColorIndex backgroundColorIndex = 0;
465 // Traverses the lines of the text.
466 for(LineIndex lineIndex = 0u; lineIndex < modelNumberOfLines; ++lineIndex)
468 const LineRun& line = *(modelLinesBuffer + lineIndex);
470 // Sets the horizontal offset of the line.
471 glyphData.horizontalOffset = ignoreHorizontalAlignment ? 0 : static_cast<int>(line.alignmentOffset);
472 glyphData.horizontalOffset += horizontalOffset;
474 // Increases the vertical offset with the line's ascender.
475 glyphData.verticalOffset += static_cast<int>(line.ascender);
477 // Include line spacing after first line
480 glyphData.verticalOffset += static_cast<int>(line.lineSpacing);
483 float left = bufferWidth;
485 float baseline = 0.0f;
487 // Traverses the glyphs of the line.
488 const GlyphIndex endGlyphIndex = std::min(numberOfGlyphs, line.glyphRun.glyphIndex + line.glyphRun.numberOfGlyphs);
489 for(GlyphIndex glyphIndex = line.glyphRun.glyphIndex; glyphIndex < endGlyphIndex; ++glyphIndex)
491 // Retrieve the glyph's info.
492 const GlyphInfo* const glyphInfo = glyphsBuffer + glyphIndex;
494 if((glyphInfo->width < Math::MACHINE_EPSILON_1000) ||
495 (glyphInfo->height < Math::MACHINE_EPSILON_1000))
497 // Nothing to do if default background color, the glyph's width or height is zero.
501 backgroundColorIndex = (nullptr == backgroundColorsBuffer) ? 0u : *(backgroundColorIndicesBuffer + glyphIndex);
503 if((backgroundColorIndex != prevBackgroundColorIndex) &&
504 (prevBackgroundColorIndex != 0u))
506 const Vector4& backgroundColor = *(backgroundColorsBuffer + prevBackgroundColorIndex - 1u);
507 DrawBackgroundColor(backgroundColor, bufferWidth, bufferHeight, glyphData, baseline, line, left, right);
510 if(backgroundColorIndex == 0u)
512 prevBackgroundColorIndex = backgroundColorIndex;
513 //if background color is the default do nothing
517 // Retrieves the glyph's position.
518 const Vector2* const position = positionBuffer + glyphIndex;
520 if(baseline < position->y + glyphInfo->yBearing)
522 baseline = position->y + glyphInfo->yBearing;
525 // Calculate the positions of leftmost and rightmost glyphs in the current line
526 if((position->x < left) || (backgroundColorIndex != prevBackgroundColorIndex))
528 left = position->x - glyphInfo->xBearing;
531 if(position->x + glyphInfo->width > right)
533 right = position->x - glyphInfo->xBearing + glyphInfo->advance;
536 prevBackgroundColorIndex = backgroundColorIndex;
539 //draw last background at line end if not default
540 if(backgroundColorIndex != 0u)
542 const Vector4& backgroundColor = *(backgroundColorsBuffer + backgroundColorIndex - 1u);
543 DrawBackgroundColor(backgroundColor, bufferWidth, bufferHeight, glyphData, baseline, line, left, right);
546 // Increases the vertical offset with the line's descender.
547 glyphData.verticalOffset += static_cast<int>(-line.descender);
550 return glyphData.bitmapBuffer;
553 /// Draws the specified strikethrough color to the buffer
554 void DrawStrikethrough(
555 const Vector4& strikethroughColor,
556 const unsigned int bufferWidth,
557 const unsigned int bufferHeight,
558 GlyphData& glyphData,
559 const float baseline,
561 const float maxStrikethroughThickness,
562 const float lineExtentLeft,
563 const float lineExtentRight,
564 float strikethroughStartingYPosition)
566 uint32_t* bitmapBuffer = reinterpret_cast<uint32_t*>(glyphData.bitmapBuffer.GetBuffer());
568 for(unsigned int y = strikethroughStartingYPosition; y < strikethroughStartingYPosition + maxStrikethroughThickness; y++)
570 if(y > bufferHeight - 1)
572 // Do not write out of bounds.
576 for(unsigned int x = glyphData.horizontalOffset + lineExtentLeft; x <= glyphData.horizontalOffset + lineExtentRight; x++)
578 if(x > bufferWidth - 1)
580 // Do not write out of bounds.
584 WriteColorToPixelBuffer(glyphData, bitmapBuffer, strikethroughColor, x, y);
591 TypesetterPtr Typesetter::New(const ModelInterface* const model)
593 return TypesetterPtr(new Typesetter(model));
596 ViewModel* Typesetter::GetViewModel()
601 Devel::PixelBuffer Typesetter::CreateImageBuffer(const unsigned int bufferWidth, const unsigned int bufferHeight, Pixel::Format pixelFormat)
603 Devel::PixelBuffer imageBuffer = Devel::PixelBuffer::New(bufferWidth, bufferHeight, pixelFormat);
605 if(Pixel::RGBA8888 == pixelFormat)
607 const unsigned int bufferSizeInt = bufferWidth * bufferHeight;
608 const unsigned int bufferSizeChar = 4u * bufferSizeInt;
609 memset(imageBuffer.GetBuffer(), 0u, bufferSizeChar);
613 memset(imageBuffer.GetBuffer(), 0, bufferWidth * bufferHeight);
619 PixelData Typesetter::Render(const Vector2& size, Toolkit::DevelText::TextDirection::Type textDirection, RenderBehaviour behaviour, bool ignoreHorizontalAlignment, Pixel::Format pixelFormat)
621 // @todo. This initial implementation for a TextLabel has only one visible page.
623 // Elides the text if needed.
624 mModel->ElideGlyphs();
626 // Retrieves the layout size.
627 const Size& layoutSize = mModel->GetLayoutSize();
629 const int outlineWidth = static_cast<int>(mModel->GetOutlineWidth());
631 // Set the offset for the horizontal alignment according to the text direction and outline width.
634 switch(mModel->GetHorizontalAlignment())
636 case HorizontalAlignment::BEGIN:
641 case HorizontalAlignment::CENTER:
643 penX += (textDirection == Toolkit::DevelText::TextDirection::LEFT_TO_RIGHT) ? -outlineWidth : outlineWidth;
646 case HorizontalAlignment::END:
648 penX += (textDirection == Toolkit::DevelText::TextDirection::LEFT_TO_RIGHT) ? -outlineWidth * 2 : outlineWidth * 2;
653 // Set the offset for the vertical alignment.
656 switch(mModel->GetVerticalAlignment())
658 case VerticalAlignment::TOP:
663 case VerticalAlignment::CENTER:
665 penY = static_cast<int>(0.5f * (size.height - layoutSize.height));
666 penY = penY < 0.f ? 0.f : penY;
669 case VerticalAlignment::BOTTOM:
671 penY = static_cast<int>(size.height - layoutSize.height);
676 // Calculate vertical line alignment
677 switch(mModel->GetVerticalLineAlignment())
679 case DevelText::VerticalLineAlignment::TOP:
683 case DevelText::VerticalLineAlignment::MIDDLE:
685 const auto& line = *mModel->GetLines();
686 penY -= line.descender;
687 penY += static_cast<int>(line.lineSpacing * 0.5f + line.descender);
690 case DevelText::VerticalLineAlignment::BOTTOM:
692 const auto& line = *mModel->GetLines();
693 const auto lineHeight = line.ascender + (-line.descender) + line.lineSpacing;
694 penY += static_cast<int>(lineHeight - (line.ascender - line.descender));
699 // Generate the image buffers of the text for each different style first,
700 // then combine all of them together as one final image buffer. We try to
701 // do all of these in CPU only, so that once the final texture is generated,
702 // no calculation is needed in GPU during each frame.
704 const unsigned int bufferWidth = static_cast<unsigned int>(size.width);
705 const unsigned int bufferHeight = static_cast<unsigned int>(size.height);
707 const unsigned int bufferSizeInt = bufferWidth * bufferHeight;
708 const unsigned int bufferSizeChar = 4u * bufferSizeInt;
710 //Elided text in ellipsis at START could start on index greater than 0
711 auto startIndexOfGlyphs = mModel->GetStartIndexOfElidedGlyphs();
712 auto endIndexOfGlyphs = mModel->GetEndIndexOfElidedGlyphs();
714 Devel::PixelBuffer imageBuffer;
716 if(RENDER_MASK == behaviour)
718 // Generate the image buffer as an alpha mask for color glyphs.
719 imageBuffer = CreateImageBuffer(bufferWidth, bufferHeight, Typesetter::STYLE_MASK, ignoreHorizontalAlignment, pixelFormat, penX, penY, startIndexOfGlyphs, endIndexOfGlyphs);
721 else if(RENDER_NO_TEXT == behaviour || RENDER_OVERLAY_STYLE == behaviour)
723 // Generate an empty image buffer so that it can been combined with the image buffers for styles
724 imageBuffer = Devel::PixelBuffer::New(bufferWidth, bufferHeight, Pixel::RGBA8888);
725 memset(imageBuffer.GetBuffer(), 0u, bufferSizeChar);
729 // Generate the image buffer for the text with no style.
730 imageBuffer = CreateImageBuffer(bufferWidth, bufferHeight, Typesetter::STYLE_NONE, ignoreHorizontalAlignment, pixelFormat, penX, penY, startIndexOfGlyphs, endIndexOfGlyphs);
733 if((RENDER_NO_STYLES != behaviour) && (RENDER_MASK != behaviour))
735 // Generate the outline if enabled
736 const uint16_t outlineWidth = mModel->GetOutlineWidth();
737 if(outlineWidth != 0u && RENDER_OVERLAY_STYLE != behaviour)
739 // Create the image buffer for outline
740 Devel::PixelBuffer outlineImageBuffer = CreateImageBuffer(bufferWidth, bufferHeight, Typesetter::STYLE_OUTLINE, ignoreHorizontalAlignment, pixelFormat, penX, penY, startIndexOfGlyphs, endIndexOfGlyphs);
742 // Combine the two buffers
743 imageBuffer = CombineImageBuffer(imageBuffer, outlineImageBuffer, bufferWidth, bufferHeight);
746 // @todo. Support shadow and underline for partial text later on.
748 // Generate the shadow if enabled
749 const Vector2& shadowOffset = mModel->GetShadowOffset();
750 if(RENDER_OVERLAY_STYLE != behaviour && (fabsf(shadowOffset.x) > Math::MACHINE_EPSILON_1 || fabsf(shadowOffset.y) > Math::MACHINE_EPSILON_1))
752 // Create the image buffer for shadow
753 Devel::PixelBuffer shadowImageBuffer = CreateImageBuffer(bufferWidth, bufferHeight, Typesetter::STYLE_SHADOW, ignoreHorizontalAlignment, pixelFormat, penX, penY, startIndexOfGlyphs, endIndexOfGlyphs);
755 // Check whether it will be a soft shadow
756 const float& blurRadius = mModel->GetShadowBlurRadius();
758 if(blurRadius > Math::MACHINE_EPSILON_1)
760 shadowImageBuffer.ApplyGaussianBlur(blurRadius);
763 // Combine the two buffers
764 imageBuffer = CombineImageBuffer(imageBuffer, shadowImageBuffer, bufferWidth, bufferHeight);
767 // Generate the underline if enabled
768 const bool underlineEnabled = mModel->IsUnderlineEnabled();
769 if(underlineEnabled && RENDER_OVERLAY_STYLE == behaviour)
771 // Create the image buffer for underline
772 Devel::PixelBuffer underlineImageBuffer = CreateImageBuffer(bufferWidth, bufferHeight, Typesetter::STYLE_UNDERLINE, ignoreHorizontalAlignment, pixelFormat, penX, penY, startIndexOfGlyphs, endIndexOfGlyphs);
774 // Combine the two buffers
775 imageBuffer = CombineImageBuffer(imageBuffer, underlineImageBuffer, bufferWidth, bufferHeight);
778 // Generate the background if enabled
779 const bool backgroundEnabled = mModel->IsBackgroundEnabled();
780 const bool backgroundMarkupSet = mModel->IsMarkupBackgroundColorSet();
781 if((backgroundEnabled || backgroundMarkupSet) && RENDER_OVERLAY_STYLE != behaviour)
783 Devel::PixelBuffer backgroundImageBuffer;
785 if(backgroundEnabled)
787 backgroundImageBuffer = CreateImageBuffer(bufferWidth, bufferHeight, Typesetter::STYLE_BACKGROUND, ignoreHorizontalAlignment, pixelFormat, penX, penY, startIndexOfGlyphs, endIndexOfGlyphs);
791 backgroundImageBuffer = CreateImageBuffer(bufferWidth, bufferHeight, pixelFormat);
794 if(backgroundMarkupSet)
796 DrawGlyphsBackground(mModel, backgroundImageBuffer, bufferWidth, bufferHeight, ignoreHorizontalAlignment, penX, penY);
799 // Combine the two buffers
800 imageBuffer = CombineImageBuffer(imageBuffer, backgroundImageBuffer, bufferWidth, bufferHeight);
803 // Generate the strikethrough if enabled
804 const bool strikethroughEnabled = mModel->IsStrikethroughEnabled();
805 if(strikethroughEnabled && RENDER_OVERLAY_STYLE == behaviour)
807 // Create the image buffer for strikethrough
808 Devel::PixelBuffer strikethroughImageBuffer = CreateImageBuffer(bufferWidth, bufferHeight, Typesetter::STYLE_STRIKETHROUGH, ignoreHorizontalAlignment, pixelFormat, penX, penY, 0u, endIndexOfGlyphs);
810 // Combine the two buffers
811 imageBuffer = CombineImageBuffer(imageBuffer, strikethroughImageBuffer, bufferWidth, bufferHeight);
816 imageBuffer = ApplyMarkupProcessorOnPixelBuffer(imageBuffer, bufferWidth, bufferHeight, ignoreHorizontalAlignment, pixelFormat, penX, penY);
819 // Create the final PixelData for the combined image buffer
820 PixelData pixelData = Devel::PixelBuffer::Convert(imageBuffer);
825 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)
827 // Retrieve lines, glyphs, positions and colors from the view model.
828 const Length modelNumberOfLines = mModel->GetNumberOfLines();
829 const LineRun* const modelLinesBuffer = mModel->GetLines();
830 const GlyphInfo* const glyphsBuffer = mModel->GetGlyphs();
831 const Vector2* const positionBuffer = mModel->GetLayout();
832 const Vector4* const colorsBuffer = mModel->GetColors();
833 const ColorIndex* const colorIndexBuffer = mModel->GetColorIndices();
834 const GlyphInfo* hyphens = mModel->GetHyphens();
835 const Length* hyphenIndices = mModel->GetHyphenIndices();
836 const Length hyphensCount = mModel->GetHyphensCount();
838 // Elided text info. Indices according to elided text and Ellipsis position.
839 const auto startIndexOfGlyphs = mModel->GetStartIndexOfElidedGlyphs();
840 const auto endIndexOfGlyphs = mModel->GetEndIndexOfElidedGlyphs();
841 const auto firstMiddleIndexOfElidedGlyphs = mModel->GetFirstMiddleIndexOfElidedGlyphs();
842 const auto secondMiddleIndexOfElidedGlyphs = mModel->GetSecondMiddleIndexOfElidedGlyphs();
843 const auto ellipsisPosition = mModel->GetEllipsisPosition();
845 // Whether to use the default color.
846 const bool useDefaultColor = (NULL == colorsBuffer);
847 const Vector4& defaultColor = mModel->GetDefaultColor();
849 // Create and initialize the pixel buffer.
851 glyphData.verticalOffset = verticalOffset;
852 glyphData.width = bufferWidth;
853 glyphData.height = bufferHeight;
854 glyphData.bitmapBuffer = CreateImageBuffer(bufferWidth, bufferHeight, pixelFormat);
855 glyphData.horizontalOffset = 0;
857 // Get a handle of the font client. Used to retrieve the bitmaps of the glyphs.
858 TextAbstraction::FontClient fontClient = TextAbstraction::FontClient::Get();
859 Length hyphenIndex = 0;
861 // Traverses the lines of the text.
862 for(LineIndex lineIndex = 0u; lineIndex < modelNumberOfLines; ++lineIndex)
864 const LineRun& line = *(modelLinesBuffer + lineIndex);
866 // Sets the horizontal offset of the line.
867 glyphData.horizontalOffset = ignoreHorizontalAlignment ? 0 : static_cast<int>(line.alignmentOffset);
868 glyphData.horizontalOffset += horizontalOffset;
870 // Increases the vertical offset with the line's ascender.
871 glyphData.verticalOffset += static_cast<int>(line.ascender);
873 // Include line spacing after first line
876 glyphData.verticalOffset += static_cast<int>(line.lineSpacing);
879 // Retrieves the glyph's outline width
880 float outlineWidth = static_cast<float>(mModel->GetOutlineWidth());
882 if(style == Typesetter::STYLE_OUTLINE)
884 glyphData.horizontalOffset -= outlineWidth;
887 // Only need to add the vertical outline offset for the first line
888 glyphData.verticalOffset -= outlineWidth;
891 else if(style == Typesetter::STYLE_SHADOW)
893 const Vector2& shadowOffset = mModel->GetShadowOffset();
894 glyphData.horizontalOffset += shadowOffset.x - outlineWidth; // if outline enabled then shadow should offset from outline
898 // Only need to add the vertical shadow offset for first line
899 glyphData.verticalOffset += shadowOffset.y - outlineWidth;
903 const bool underlineEnabled = mModel->IsUnderlineEnabled();
904 const Vector4& underlineColor = mModel->GetUnderlineColor();
905 const float underlineHeight = mModel->GetUnderlineHeight();
907 const bool strikethroughEnabled = mModel->IsStrikethroughEnabled();
908 const Vector4& strikethroughColor = mModel->GetStrikethroughColor();
909 const float strikethroughHeight = mModel->GetStrikethroughHeight();
911 // Get the underline runs.
912 const Length numberOfUnderlineRuns = mModel->GetNumberOfUnderlineRuns();
913 Vector<GlyphRun> underlineRuns;
914 underlineRuns.Resize(numberOfUnderlineRuns);
915 mModel->GetUnderlineRuns(underlineRuns.Begin(), 0u, numberOfUnderlineRuns);
917 bool thereAreUnderlinedGlyphs = false;
918 bool strikethroughGlyphsExist = false;
920 float currentUnderlinePosition = 0.0f;
921 float currentUnderlineThickness = underlineHeight;
922 float maxUnderlineThickness = currentUnderlineThickness;
923 float currentStrikethroughThickness = strikethroughHeight;
924 float maxStrikethroughThickness = currentStrikethroughThickness;
925 float strikethroughStartingYPosition = 0.0f;
927 FontId lastUnderlinedFontId = 0;
929 float lineExtentLeft = bufferWidth;
930 float lineExtentRight = 0.0f;
931 float baseline = 0.0f;
932 bool addHyphen = false;
934 // Traverses the glyphs of the line.
935 const GlyphIndex startGlyphIndex = std::max(line.glyphRun.glyphIndex, startIndexOfGlyphs);
936 GlyphIndex endGlyphIndex = (line.isSplitToTwoHalves ? line.glyphRunSecondHalf.glyphIndex + line.glyphRunSecondHalf.numberOfGlyphs : line.glyphRun.glyphIndex + line.glyphRun.numberOfGlyphs) - 1u;
937 endGlyphIndex = std::min(endGlyphIndex, endIndexOfGlyphs);
939 for(GlyphIndex glyphIndex = startGlyphIndex; glyphIndex <= endGlyphIndex; ++glyphIndex)
941 if(glyphIndex < fromGlyphIndex || glyphIndex > toGlyphIndex)
943 // Ignore any glyph that out of the specified range
947 //To handle START case of ellipsis, the first glyph has been shifted
948 //glyphIndex represent indices in whole glyphs but elidedGlyphIndex represents indices in elided Glyphs
949 GlyphIndex elidedGlyphIndex = glyphIndex - startIndexOfGlyphs;
951 //To handle MIDDLE case of ellipsis, the first glyph in the second half of line has been shifted and skip the removed glyph from middle.
952 if(ellipsisPosition == DevelText::EllipsisPosition::MIDDLE)
954 if(glyphIndex > firstMiddleIndexOfElidedGlyphs &&
955 glyphIndex < secondMiddleIndexOfElidedGlyphs)
957 // Ignore any glyph that removed for MIDDLE ellipsis
960 if(glyphIndex >= secondMiddleIndexOfElidedGlyphs)
962 elidedGlyphIndex -= (secondMiddleIndexOfElidedGlyphs - firstMiddleIndexOfElidedGlyphs - 1u);
966 // Retrieve the glyph's info.
967 const GlyphInfo* glyphInfo;
969 if(addHyphen && hyphens)
971 glyphInfo = hyphens + hyphenIndex;
976 glyphInfo = glyphsBuffer + elidedGlyphIndex;
979 if((glyphInfo->width < Math::MACHINE_EPSILON_1000) ||
980 (glyphInfo->height < Math::MACHINE_EPSILON_1000))
982 // Nothing to do if the glyph's width or height is zero.
986 const bool underlineGlyph = underlineEnabled || IsGlyphUnderlined(glyphIndex, underlineRuns);
987 thereAreUnderlinedGlyphs = thereAreUnderlinedGlyphs || underlineGlyph;
989 strikethroughGlyphsExist = strikethroughGlyphsExist || strikethroughEnabled;
991 // Are we still using the same fontId as previous
992 if((strikethroughEnabled || underlineGlyph) && (glyphInfo->fontId != lastUnderlinedFontId))
994 // We need to fetch fresh font underline metrics
995 FetchFontDecorationlinesMetrics(fontClient, glyphInfo, currentUnderlinePosition, underlineHeight, currentUnderlineThickness, maxUnderlineThickness, lastUnderlinedFontId, strikethroughHeight, currentStrikethroughThickness, maxStrikethroughThickness);
998 // Retrieves the glyph's position.
999 Vector2 position = *(positionBuffer + elidedGlyphIndex);
1003 GlyphInfo tempInfo = *(glyphsBuffer + elidedGlyphIndex);
1004 position.x = position.x + tempInfo.advance - tempInfo.xBearing + glyphInfo->xBearing;
1005 position.y = -glyphInfo->yBearing;
1008 if(baseline < position.y + glyphInfo->yBearing)
1010 baseline = position.y + glyphInfo->yBearing;
1013 // Calculate the positions of leftmost and rightmost glyphs in the current line
1014 if(position.x < lineExtentLeft)
1016 lineExtentLeft = position.x;
1019 if(position.x + glyphInfo->width > lineExtentRight)
1021 lineExtentRight = position.x + glyphInfo->width;
1024 // Retrieves the glyph's color.
1025 const ColorIndex colorIndex = useDefaultColor ? 0u : *(colorIndexBuffer + glyphIndex);
1028 if(style == Typesetter::STYLE_SHADOW)
1030 color = mModel->GetShadowColor();
1032 else if(style == Typesetter::STYLE_OUTLINE)
1034 color = mModel->GetOutlineColor();
1038 color = (useDefaultColor || (0u == colorIndex)) ? defaultColor : *(colorsBuffer + (colorIndex - 1u));
1041 // Premultiply alpha
1046 // Retrieves the glyph's bitmap.
1047 glyphData.glyphBitmap.buffer = NULL;
1048 glyphData.glyphBitmap.width = glyphInfo->width; // Desired width and height.
1049 glyphData.glyphBitmap.height = glyphInfo->height;
1051 if(style != Typesetter::STYLE_OUTLINE && style != Typesetter::STYLE_SHADOW)
1053 // Don't render outline for other styles
1054 outlineWidth = 0.0f;
1057 if(style != Typesetter::STYLE_UNDERLINE && style != Typesetter::STYLE_STRIKETHROUGH)
1059 fontClient.CreateBitmap(glyphInfo->fontId,
1061 glyphInfo->isItalicRequired,
1062 glyphInfo->isBoldRequired,
1063 glyphData.glyphBitmap,
1064 static_cast<int>(outlineWidth));
1067 // Sets the glyph's bitmap into the bitmap of the whole text.
1068 if(NULL != glyphData.glyphBitmap.buffer)
1070 if(style == Typesetter::STYLE_OUTLINE)
1072 // Set the position offset for the current glyph
1073 glyphData.horizontalOffset -= glyphData.glyphBitmap.outlineOffsetX;
1074 glyphData.verticalOffset -= glyphData.glyphBitmap.outlineOffsetY;
1077 // Set the buffer of the glyph's bitmap into the final bitmap's buffer
1078 TypesetGlyph(glyphData,
1084 if(style == Typesetter::STYLE_OUTLINE)
1086 // Reset the position offset for the next glyph
1087 glyphData.horizontalOffset += glyphData.glyphBitmap.outlineOffsetX;
1088 glyphData.verticalOffset += glyphData.glyphBitmap.outlineOffsetY;
1091 // delete the glyphBitmap.buffer as it is now copied into glyphData.bitmapBuffer
1092 delete[] glyphData.glyphBitmap.buffer;
1093 glyphData.glyphBitmap.buffer = NULL;
1098 while((hyphenIndex < hyphensCount) && (glyphIndex > hyphenIndices[hyphenIndex]))
1103 addHyphen = ((hyphenIndex < hyphensCount) && ((glyphIndex + 1) == hyphenIndices[hyphenIndex]));
1111 // Draw the underline from the leftmost glyph to the rightmost glyph
1112 if(thereAreUnderlinedGlyphs && style == Typesetter::STYLE_UNDERLINE)
1114 DrawUnderline(underlineColor, bufferWidth, bufferHeight, glyphData, baseline, currentUnderlinePosition, maxUnderlineThickness, lineExtentLeft, lineExtentRight);
1117 // Draw the background color from the leftmost glyph to the rightmost glyph
1118 if(style == Typesetter::STYLE_BACKGROUND)
1120 DrawBackgroundColor(mModel->GetBackgroundColor(), bufferWidth, bufferHeight, glyphData, baseline, line, lineExtentLeft, lineExtentRight);
1123 // Draw the strikethrough from the leftmost glyph to the rightmost glyph
1124 if(strikethroughGlyphsExist && style == Typesetter::STYLE_STRIKETHROUGH)
1126 //TODO : The currently implemented strikethrough creates a strikethrough on the line level. We need to create different strikethroughs the case of glyphs with different sizes.
1127 strikethroughStartingYPosition = (glyphData.verticalOffset + baseline + currentUnderlinePosition) - ((line.ascender) * HALF); // Since Free Type font doesn't contain the strikethrough-position property, strikethrough position will be calculated by moving the underline position upwards by half the value of the line height.
1128 DrawStrikethrough(strikethroughColor, bufferWidth, bufferHeight, glyphData, baseline, line, maxStrikethroughThickness, lineExtentLeft, lineExtentRight, strikethroughStartingYPosition);
1131 // Increases the vertical offset with the line's descender.
1132 glyphData.verticalOffset += static_cast<int>(-line.descender);
1135 return glyphData.bitmapBuffer;
1138 Devel::PixelBuffer Typesetter::CombineImageBuffer(Devel::PixelBuffer topPixelBuffer, Devel::PixelBuffer bottomPixelBuffer, const unsigned int bufferWidth, const unsigned int bufferHeight)
1140 unsigned char* topBuffer = topPixelBuffer.GetBuffer();
1141 unsigned char* bottomBuffer = bottomPixelBuffer.GetBuffer();
1143 Devel::PixelBuffer combinedPixelBuffer;
1145 if(topBuffer == NULL && bottomBuffer == NULL)
1147 // Nothing to do if both buffers are empty.
1148 return combinedPixelBuffer;
1151 if(topBuffer == NULL)
1153 // Nothing to do if topBuffer is empty.
1154 return bottomPixelBuffer;
1157 if(bottomBuffer == NULL)
1159 // Nothing to do if bottomBuffer is empty.
1160 return topPixelBuffer;
1163 // Always combine two RGBA images
1164 const unsigned int bufferSizeInt = bufferWidth * bufferHeight;
1165 const unsigned int bufferSizeChar = 4u * bufferSizeInt;
1167 combinedPixelBuffer = Devel::PixelBuffer::New(bufferWidth, bufferHeight, Pixel::RGBA8888);
1168 uint8_t* combinedBuffer = reinterpret_cast<uint8_t*>(combinedPixelBuffer.GetBuffer());
1169 memset(combinedBuffer, 0u, bufferSizeChar);
1171 for(unsigned int pixelIndex = 0; pixelIndex < bufferSizeInt; pixelIndex++)
1173 // If the alpha of the pixel in either buffer is not fully opaque, blend the two pixels.
1174 // Otherwise, copy pixel from topBuffer to combinedBuffer.
1176 unsigned int alphaBuffer1 = topBuffer[pixelIndex * 4 + 3];
1178 if(alphaBuffer1 != 255)
1180 // At least one pixel is not fully opaque
1181 // "Over" blend the the pixel from topBuffer with the pixel in bottomBuffer
1182 combinedBuffer[pixelIndex * 4] = topBuffer[pixelIndex * 4] + (bottomBuffer[pixelIndex * 4] * (255 - topBuffer[pixelIndex * 4 + 3]) / 255);
1183 combinedBuffer[pixelIndex * 4 + 1] = topBuffer[pixelIndex * 4 + 1] + (bottomBuffer[pixelIndex * 4 + 1] * (255 - topBuffer[pixelIndex * 4 + 3]) / 255);
1184 combinedBuffer[pixelIndex * 4 + 2] = topBuffer[pixelIndex * 4 + 2] + (bottomBuffer[pixelIndex * 4 + 2] * (255 - topBuffer[pixelIndex * 4 + 3]) / 255);
1185 combinedBuffer[pixelIndex * 4 + 3] = topBuffer[pixelIndex * 4 + 3] + (bottomBuffer[pixelIndex * 4 + 3] * (255 - topBuffer[pixelIndex * 4 + 3]) / 255);
1189 // Copy the pixel from topBuffer to combinedBuffer
1190 combinedBuffer[pixelIndex * 4] = topBuffer[pixelIndex * 4];
1191 combinedBuffer[pixelIndex * 4 + 1] = topBuffer[pixelIndex * 4 + 1];
1192 combinedBuffer[pixelIndex * 4 + 2] = topBuffer[pixelIndex * 4 + 2];
1193 combinedBuffer[pixelIndex * 4 + 3] = topBuffer[pixelIndex * 4 + 3];
1197 return combinedPixelBuffer;
1200 Devel::PixelBuffer Typesetter::ApplyMarkupProcessorOnPixelBuffer(Devel::PixelBuffer topPixelBuffer, const unsigned int bufferWidth, const unsigned int bufferHeight, bool ignoreHorizontalAlignment, Pixel::Format pixelFormat, int horizontalOffset, int verticalOffset)
1202 // Apply the markup-Processor if enabled
1203 const bool markupProcessorEnabled = mModel->IsMarkupProcessorEnabled();
1204 if(markupProcessorEnabled)
1206 // Underline-tags (this is for Markup case)
1207 // Get the underline runs.
1208 const Length numberOfUnderlineRuns = mModel->GetNumberOfUnderlineRuns();
1209 Vector<GlyphRun> underlineRuns;
1210 underlineRuns.Resize(numberOfUnderlineRuns);
1211 mModel->GetUnderlineRuns(underlineRuns.Begin(), 0u, numberOfUnderlineRuns);
1213 // Iterate on the consecutive underlined glyph run and connect them into one chunk of underlined characters.
1214 Vector<GlyphRun>::ConstIterator itGlyphRun = underlineRuns.Begin();
1215 Vector<GlyphRun>::ConstIterator endItGlyphRun = underlineRuns.End();
1216 GlyphIndex startGlyphIndex, endGlyphIndex;
1218 //The outer loop to iterate on the separated chunks of underlined glyph runs
1219 while(itGlyphRun != endItGlyphRun)
1221 startGlyphIndex = itGlyphRun->glyphIndex;
1222 endGlyphIndex = startGlyphIndex;
1223 //The inner loop to make a connected underline for the consecutive characters
1226 endGlyphIndex += itGlyphRun->numberOfGlyphs;
1228 } while(itGlyphRun != endItGlyphRun && itGlyphRun->glyphIndex == endGlyphIndex);
1232 // Create the image buffer for underline
1233 Devel::PixelBuffer underlineImageBuffer = CreateImageBuffer(bufferWidth, bufferHeight, Typesetter::STYLE_UNDERLINE, ignoreHorizontalAlignment, pixelFormat, horizontalOffset, verticalOffset, startGlyphIndex, endGlyphIndex);
1234 // Combine the two buffers
1235 topPixelBuffer = CombineImageBuffer(topPixelBuffer, underlineImageBuffer, bufferWidth, bufferHeight);
1239 return topPixelBuffer;
1242 Typesetter::Typesetter(const ModelInterface* const model)
1243 : mModel(new ViewModel(model))
1247 Typesetter::~Typesetter()
1254 } // namespace Toolkit