2 * Copyright (c) 2022 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/glyph-metrics-helper.h>
29 #include <dali-toolkit/internal/text/line-helper-functions.h>
30 #include <dali-toolkit/internal/text/rendering/styles/character-spacing-helper-functions.h>
31 #include <dali-toolkit/internal/text/rendering/styles/strikethrough-helper-functions.h>
32 #include <dali-toolkit/internal/text/rendering/styles/underline-helper-functions.h>
33 #include <dali-toolkit/internal/text/rendering/view-model.h>
43 const float HALF(0.5f);
44 const float ONE_AND_A_HALF(1.5f);
47 * @brief Fast multiply & divide by 255. It wiil be useful when we applying alpha value in color
49 * @param x The value between [0..255]
50 * @param y The value between [0..255]
53 inline uint8_t MultiplyAndNormalizeColor(const uint8_t x, const uint8_t y) noexcept
55 const uint32_t xy = static_cast<const uint32_t>(x) * y;
56 return ((xy << 15) + (xy << 7) + xy) >> 23;
60 * @brief Data struct used to set the buffer of the glyph's bitmap into the final bitmap's buffer.
64 Devel::PixelBuffer bitmapBuffer; ///< The buffer of the whole bitmap. The format is RGBA8888.
65 Vector2* position; ///< The position of the glyph.
66 TextAbstraction::FontClient::GlyphBufferData glyphBitmap; ///< The glyph's bitmap.
67 uint32_t width; ///< The bitmap's width.
68 uint32_t height; ///< The bitmap's height.
69 int32_t horizontalOffset; ///< The horizontal offset to be added to the 'x' glyph's position.
70 int32_t verticalOffset; ///< The vertical offset to be added to the 'y' glyph's position.
74 * @brief Sets the glyph's buffer into the bitmap's buffer.
76 * @param[in, out] data Struct which contains the glyph's data and the bitmap's data.
77 * @param[in] position The position of the glyph.
78 * @param[in] color The color of the glyph.
79 * @param[in] style The style of the text.
80 * @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).
82 void TypesetGlyph(GlyphData& __restrict__ data,
83 const Vector2* const __restrict__ position,
84 const Vector4* const __restrict__ color,
85 const Typesetter::Style style,
86 const Pixel::Format pixelFormat)
88 if((0u == data.glyphBitmap.width) || (0u == data.glyphBitmap.height))
90 // Nothing to do if the width or height of the buffer is zero.
94 // Initial vertical / horizontal offset.
95 const int32_t yOffset = data.verticalOffset + position->y;
96 const int32_t xOffset = data.horizontalOffset + position->x;
98 // Whether the given glyph is a color one.
99 const bool isColorGlyph = data.glyphBitmap.isColorEmoji || data.glyphBitmap.isColorBitmap;
100 const uint32_t glyphPixelSize = Pixel::GetBytesPerPixel(data.glyphBitmap.format);
101 const uint32_t glyphAlphaIndex = glyphPixelSize - 1u;
103 // Determinate iterator range.
104 const int32_t lineIndexRangeMin = std::max(0, -yOffset);
105 const int32_t lineIndexRangeMax = std::min(static_cast<int32_t>(data.glyphBitmap.height), static_cast<int32_t>(data.height) - yOffset);
106 const int32_t indexRangeMin = std::max(0, -xOffset);
107 const int32_t indexRangeMax = std::min(static_cast<int32_t>(data.glyphBitmap.width), static_cast<int32_t>(data.width) - xOffset);
109 // If current glyph don't need to be rendered, just ignore.
110 if(lineIndexRangeMax <= lineIndexRangeMin || indexRangeMax <= indexRangeMin)
115 if(Pixel::RGBA8888 == pixelFormat)
117 uint32_t* __restrict__ bitmapBuffer = reinterpret_cast<uint32_t*>(data.bitmapBuffer.GetBuffer());
119 bitmapBuffer += (lineIndexRangeMin + yOffset) * static_cast<int32_t>(data.width);
121 // Fast-cut if style is MASK or OUTLINE. Outline not shown for color glyph.
122 // Just overwrite transparent color and return.
123 if(isColorGlyph && (Typesetter::STYLE_MASK == style || Typesetter::STYLE_OUTLINE == style))
125 for(int32_t lineIndex = lineIndexRangeMin; lineIndex < lineIndexRangeMax; ++lineIndex)
127 // We can use memset here.
128 memset(bitmapBuffer + xOffset + indexRangeMin, 0, (indexRangeMax - indexRangeMin) * sizeof(uint32_t));
129 bitmapBuffer += data.width;
134 const bool swapChannelsBR = Pixel::BGRA8888 == data.glyphBitmap.format;
136 // Offset byte value of glyph bitmap.
137 uint32_t glyphOffet = 0u;
139 // Allocate scanline memory for glyph bitmap if we need.
140 const bool useLocalScanline = data.glyphBitmap.compressionType != TextAbstraction::FontClient::GlyphBufferData::CompressionType::NO_COMPRESSION;
141 uint8_t* __restrict__ glyphScanline = useLocalScanline ? (uint8_t*)malloc(data.glyphBitmap.width * glyphPixelSize) : data.glyphBitmap.buffer;
143 // Precalculate input color's packed result.
144 uint32_t packedInputColor = 0u;
145 uint8_t* __restrict__ packedInputColorBuffer = reinterpret_cast<uint8_t*>(&packedInputColor);
147 *(packedInputColorBuffer + 3u) = static_cast<uint8_t>(color->a * 255);
148 *(packedInputColorBuffer + 2u) = static_cast<uint8_t>(color->b * 255);
149 *(packedInputColorBuffer + 1u) = static_cast<uint8_t>(color->g * 255);
150 *(packedInputColorBuffer) = static_cast<uint8_t>(color->r * 255);
152 // Skip basic line of glyph.
155 for(int32_t lineIndex = 0; lineIndex < lineIndexRangeMin; ++lineIndex)
157 TextAbstraction::FontClient::GlyphBufferData::DecompressScanline(data.glyphBitmap, glyphScanline, glyphOffet);
162 glyphScanline += lineIndexRangeMin * static_cast<int32_t>(data.glyphBitmap.width * glyphPixelSize);
165 // Traverse the pixels of the glyph line per line.
168 for(int32_t lineIndex = lineIndexRangeMin; lineIndex < lineIndexRangeMax; ++lineIndex)
172 TextAbstraction::FontClient::GlyphBufferData::DecompressScanline(data.glyphBitmap, glyphScanline, glyphOffet);
175 for(int32_t index = indexRangeMin; index < indexRangeMax; ++index)
177 const int32_t xOffsetIndex = xOffset + index;
179 // Retrieves the color from the color glyph.
180 uint32_t packedColorGlyph = *(reinterpret_cast<const uint32_t*>(glyphScanline + (index << 2)));
181 uint8_t* __restrict__ packedColorGlyphBuffer = reinterpret_cast<uint8_t*>(&packedColorGlyph);
183 // Update the alpha channel.
184 const uint8_t colorAlpha = MultiplyAndNormalizeColor(*(packedInputColorBuffer + 3u), *(packedColorGlyphBuffer + 3u));
185 *(packedColorGlyphBuffer + 3u) = colorAlpha;
187 if(Typesetter::STYLE_SHADOW == style)
189 // The shadow of color glyph needs to have the shadow color.
190 *(packedColorGlyphBuffer + 2u) = MultiplyAndNormalizeColor(*(packedInputColorBuffer + 2u), colorAlpha);
191 *(packedColorGlyphBuffer + 1u) = MultiplyAndNormalizeColor(*(packedInputColorBuffer + 1u), colorAlpha);
192 *packedColorGlyphBuffer = MultiplyAndNormalizeColor(*packedInputColorBuffer, colorAlpha);
198 std::swap(*packedColorGlyphBuffer, *(packedColorGlyphBuffer + 2u)); // Swap B and R.
201 *(packedColorGlyphBuffer + 2u) = MultiplyAndNormalizeColor(*(packedColorGlyphBuffer + 2u), colorAlpha);
202 *(packedColorGlyphBuffer + 1u) = MultiplyAndNormalizeColor(*(packedColorGlyphBuffer + 1u), colorAlpha);
203 *packedColorGlyphBuffer = MultiplyAndNormalizeColor(*packedColorGlyphBuffer, colorAlpha);
205 if(data.glyphBitmap.isColorBitmap)
207 *(packedColorGlyphBuffer + 2u) = MultiplyAndNormalizeColor(*(packedInputColorBuffer + 2u), *(packedColorGlyphBuffer + 2u));
208 *(packedColorGlyphBuffer + 1u) = MultiplyAndNormalizeColor(*(packedInputColorBuffer + 1u), *(packedColorGlyphBuffer + 1u));
209 *packedColorGlyphBuffer = MultiplyAndNormalizeColor(*packedInputColorBuffer, *packedColorGlyphBuffer);
213 // Set the color into the final pixel buffer.
214 *(bitmapBuffer + xOffsetIndex) = packedColorGlyph;
217 bitmapBuffer += data.width;
218 if(!useLocalScanline)
220 glyphScanline += data.glyphBitmap.width * glyphPixelSize;
226 for(int32_t lineIndex = lineIndexRangeMin; lineIndex < lineIndexRangeMax; ++lineIndex)
230 TextAbstraction::FontClient::GlyphBufferData::DecompressScanline(data.glyphBitmap, glyphScanline, glyphOffet);
233 for(int32_t index = indexRangeMin; index < indexRangeMax; ++index)
235 // Update the alpha channel.
236 const uint8_t alpha = *(glyphScanline + index * glyphPixelSize + glyphAlphaIndex);
238 // Copy non-transparent pixels only
241 const int32_t xOffsetIndex = xOffset + index;
243 // Check alpha of overlapped pixels
244 uint32_t& currentColor = *(bitmapBuffer + xOffsetIndex);
245 uint8_t* packedCurrentColorBuffer = reinterpret_cast<uint8_t*>(¤tColor);
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 uint8_t currentAlpha = *(packedCurrentColorBuffer + 3u);
252 currentAlpha = std::max(currentAlpha, alpha);
253 if(currentAlpha == 255)
255 // Fast-cut to avoid float type operation.
256 currentColor = packedInputColor;
260 // Pack the given color into a 32bit buffer. The alpha channel will be updated later for each pixel.
261 // The format is RGBA8888.
262 uint32_t packedColor = 0u;
263 uint8_t* __restrict__ packedColorBuffer = reinterpret_cast<uint8_t*>(&packedColor);
265 // Color is pre-muliplied with its alpha.
266 *(packedColorBuffer + 3u) = MultiplyAndNormalizeColor(*(packedInputColorBuffer + 3u), currentAlpha);
267 *(packedColorBuffer + 2u) = MultiplyAndNormalizeColor(*(packedInputColorBuffer + 2u), currentAlpha);
268 *(packedColorBuffer + 1u) = MultiplyAndNormalizeColor(*(packedInputColorBuffer + 1u), currentAlpha);
269 *(packedColorBuffer) = MultiplyAndNormalizeColor(*packedInputColorBuffer, currentAlpha);
271 // Set the color into the final pixel buffer.
272 currentColor = packedColor;
277 bitmapBuffer += data.width;
278 if(!useLocalScanline)
280 glyphScanline += data.glyphBitmap.width * glyphPixelSize;
292 // Below codes required only if not color glyph.
295 uint8_t* __restrict__ bitmapBuffer = data.bitmapBuffer.GetBuffer();
297 // Offset byte value of glyph bitmap.
298 uint32_t glyphOffet = 0u;
300 // Allocate scanline memory for glyph bitmap if we need.
301 const bool useLocalScanline = data.glyphBitmap.compressionType != TextAbstraction::FontClient::GlyphBufferData::CompressionType::NO_COMPRESSION;
302 uint8_t* __restrict__ glyphScanline = useLocalScanline ? (uint8_t*)malloc(data.glyphBitmap.width * glyphPixelSize) : data.glyphBitmap.buffer;
305 bitmapBuffer += (lineIndexRangeMin + yOffset) * static_cast<int32_t>(data.width);
307 // Skip basic line of glyph.
310 for(int32_t lineIndex = 0; lineIndex < lineIndexRangeMin; ++lineIndex)
312 TextAbstraction::FontClient::GlyphBufferData::DecompressScanline(data.glyphBitmap, glyphScanline, glyphOffet);
317 glyphScanline += lineIndexRangeMin * static_cast<int32_t>(data.glyphBitmap.width * glyphPixelSize);
320 // Traverse the pixels of the glyph line per line.
321 for(int32_t lineIndex = lineIndexRangeMin; lineIndex < lineIndexRangeMax; ++lineIndex)
325 TextAbstraction::FontClient::GlyphBufferData::DecompressScanline(data.glyphBitmap, glyphScanline, glyphOffet);
328 for(int32_t index = indexRangeMin; index < indexRangeMax; ++index)
330 const int32_t xOffsetIndex = xOffset + index;
332 // Update the alpha channel.
333 const uint8_t alpha = *(glyphScanline + index * glyphPixelSize + glyphAlphaIndex);
335 // Copy non-transparent pixels only
338 // Check alpha of overlapped pixels
339 uint8_t& currentAlpha = *(bitmapBuffer + xOffsetIndex);
341 // For any pixel overlapped with the pixel in previous glyphs, make sure we don't
342 // overwrite a previous bigger alpha with a smaller alpha (in order to avoid
343 // semi-transparent gaps between joint glyphs with overlapped pixels, which could
344 // happen, for example, in the RTL text when we copy glyphs from right to left).
345 currentAlpha = std::max(currentAlpha, alpha);
349 bitmapBuffer += data.width;
350 if(!useLocalScanline)
352 glyphScanline += data.glyphBitmap.width * glyphPixelSize;
364 /// Draws the specified underline color to the buffer
366 const uint32_t bufferWidth,
367 const uint32_t bufferHeight,
368 GlyphData& glyphData,
369 const float baseline,
370 const float currentUnderlinePosition,
371 const float maxUnderlineHeight,
372 const float lineExtentLeft,
373 const float lineExtentRight,
374 const UnderlineStyleProperties& commonUnderlineProperties,
375 const UnderlineStyleProperties& currentUnderlineProperties,
378 const Vector4& underlineColor = currentUnderlineProperties.colorDefined ? currentUnderlineProperties.color : commonUnderlineProperties.color;
379 const Text::Underline::Type underlineType = currentUnderlineProperties.typeDefined ? currentUnderlineProperties.type : commonUnderlineProperties.type;
380 const float dashedUnderlineWidth = currentUnderlineProperties.dashWidthDefined ? currentUnderlineProperties.dashWidth : commonUnderlineProperties.dashWidth;
381 const float dashedUnderlineGap = currentUnderlineProperties.dashGapDefined ? currentUnderlineProperties.dashGap : commonUnderlineProperties.dashGap;
383 int32_t underlineYOffset = glyphData.verticalOffset + baseline + currentUnderlinePosition;
385 const uint32_t yRangeMin = underlineYOffset;
386 const uint32_t yRangeMax = std::min(bufferHeight, underlineYOffset + static_cast<uint32_t>(maxUnderlineHeight));
387 const uint32_t xRangeMin = static_cast<uint32_t>(glyphData.horizontalOffset + lineExtentLeft);
388 const uint32_t xRangeMax = std::min(bufferWidth, static_cast<uint32_t>(glyphData.horizontalOffset + lineExtentRight + 1)); // Due to include last point, we add 1 here
390 // If current glyph don't need to be rendered, just ignore.
391 if((underlineType != Text::Underline::DOUBLE && yRangeMax <= yRangeMin) || xRangeMax <= xRangeMin)
396 // We can optimize by memset when underlineColor.a is near zero
397 uint8_t underlineColorAlpha = static_cast<uint8_t>(underlineColor.a * 255.f);
399 uint32_t* bitmapBuffer = reinterpret_cast<uint32_t*>(glyphData.bitmapBuffer.GetBuffer());
401 // Skip yRangeMin line.
402 bitmapBuffer += yRangeMin * glyphData.width;
404 // Note if underlineType is DASHED, we cannot setup color by memset.
405 if(underlineType != Text::Underline::DASHED && underlineColorAlpha == 0)
407 for(uint32_t y = yRangeMin; y < yRangeMax; y++)
409 // We can use memset.
410 memset(bitmapBuffer + xRangeMin, 0, (xRangeMax - xRangeMin) * sizeof(uint32_t));
411 bitmapBuffer += glyphData.width;
413 if(underlineType == Text::Underline::DOUBLE)
415 int32_t secondUnderlineYOffset = underlineYOffset - ONE_AND_A_HALF * maxUnderlineHeight;
416 const uint32_t secondYRangeMin = static_cast<uint32_t>(std::max(0, secondUnderlineYOffset));
417 const uint32_t secondYRangeMax = static_cast<uint32_t>(std::max(0, std::min(static_cast<int32_t>(bufferHeight), secondUnderlineYOffset + static_cast<int32_t>(maxUnderlineHeight))));
419 // Rewind bitmapBuffer pointer, and skip secondYRangeMin line.
420 bitmapBuffer = reinterpret_cast<uint32_t*>(glyphData.bitmapBuffer.GetBuffer()) + yRangeMin * glyphData.width;
422 for(uint32_t y = secondYRangeMin; y < secondYRangeMax; y++)
424 // We can use memset.
425 memset(bitmapBuffer + xRangeMin, 0, (xRangeMax - xRangeMin) * sizeof(uint32_t));
426 bitmapBuffer += glyphData.width;
432 uint32_t packedUnderlineColor = 0u;
433 uint8_t* packedUnderlineColorBuffer = reinterpret_cast<uint8_t*>(&packedUnderlineColor);
435 // Write the color to the pixel buffer
436 *(packedUnderlineColorBuffer + 3u) = underlineColorAlpha;
437 *(packedUnderlineColorBuffer + 2u) = static_cast<uint8_t>(underlineColor.b * underlineColorAlpha);
438 *(packedUnderlineColorBuffer + 1u) = static_cast<uint8_t>(underlineColor.g * underlineColorAlpha);
439 *(packedUnderlineColorBuffer) = static_cast<uint8_t>(underlineColor.r * underlineColorAlpha);
441 for(uint32_t y = yRangeMin; y < yRangeMax; y++)
443 if(underlineType == Text::Underline::DASHED)
445 float dashWidth = dashedUnderlineWidth;
448 for(uint32_t x = xRangeMin; x < xRangeMax; x++)
450 if(dashGap == 0 && dashWidth > 0)
452 // Note : this is same logic as bitmap[y][x] = underlineColor;
453 *(bitmapBuffer + x) = packedUnderlineColor;
456 else if(dashGap < dashedUnderlineGap)
463 dashWidth = dashedUnderlineWidth;
470 for(uint32_t x = xRangeMin; x < xRangeMax; x++)
472 // Note : this is same logic as bitmap[y][x] = underlineColor;
473 *(bitmapBuffer + x) = packedUnderlineColor;
476 bitmapBuffer += glyphData.width;
478 if(underlineType == Text::Underline::DOUBLE)
480 int32_t secondUnderlineYOffset = underlineYOffset - ONE_AND_A_HALF * maxUnderlineHeight;
481 const uint32_t secondYRangeMin = static_cast<uint32_t>(std::max(0, secondUnderlineYOffset));
482 const uint32_t secondYRangeMax = static_cast<uint32_t>(std::max(0, std::min(static_cast<int32_t>(bufferHeight), secondUnderlineYOffset + static_cast<int32_t>(maxUnderlineHeight))));
484 // Rewind bitmapBuffer pointer, and skip secondYRangeMin line.
485 bitmapBuffer = reinterpret_cast<uint32_t*>(glyphData.bitmapBuffer.GetBuffer()) + yRangeMin * glyphData.width;
487 for(uint32_t y = secondYRangeMin; y < secondYRangeMax; y++)
489 for(uint32_t x = xRangeMin; x < xRangeMax; x++)
491 // Note : this is same logic as bitmap[y][x] = underlineColor;
492 *(bitmapBuffer + x) = packedUnderlineColor;
494 bitmapBuffer += glyphData.width;
500 /// Draws the background color to the buffer
501 void DrawBackgroundColor(
502 Vector4 backgroundColor,
503 const uint32_t bufferWidth,
504 const uint32_t bufferHeight,
505 GlyphData& glyphData,
506 const float baseline,
508 const float lineExtentLeft,
509 const float lineExtentRight)
511 const int32_t yRangeMin = std::max(0, static_cast<int32_t>(glyphData.verticalOffset + baseline - line.ascender));
512 const int32_t yRangeMax = std::min(static_cast<int32_t>(bufferHeight), static_cast<int32_t>(glyphData.verticalOffset + baseline - line.descender));
513 const int32_t xRangeMin = std::max(0, static_cast<int32_t>(glyphData.horizontalOffset + lineExtentLeft));
514 const int32_t xRangeMax = std::min(static_cast<int32_t>(bufferWidth), static_cast<int32_t>(glyphData.horizontalOffset + lineExtentRight + 1)); // Due to include last point, we add 1 here
516 // If current glyph don't need to be rendered, just ignore.
517 if(yRangeMax <= yRangeMin || xRangeMax <= xRangeMin)
522 // We can optimize by memset when backgroundColor.a is near zero
523 uint8_t backgroundColorAlpha = static_cast<uint8_t>(backgroundColor.a * 255.f);
525 uint32_t* bitmapBuffer = reinterpret_cast<uint32_t*>(glyphData.bitmapBuffer.GetBuffer());
527 // Skip yRangeMin line.
528 bitmapBuffer += yRangeMin * glyphData.width;
530 if(backgroundColorAlpha == 0)
532 for(int32_t y = yRangeMin; y < yRangeMax; y++)
534 // We can use memset.
535 memset(bitmapBuffer + xRangeMin, 0, (xRangeMax - xRangeMin) * sizeof(uint32_t));
536 bitmapBuffer += glyphData.width;
541 uint32_t packedBackgroundColor = 0u;
542 uint8_t* packedBackgroundColorBuffer = reinterpret_cast<uint8_t*>(&packedBackgroundColor);
544 // Write the color to the pixel buffer
545 *(packedBackgroundColorBuffer + 3u) = backgroundColorAlpha;
546 *(packedBackgroundColorBuffer + 2u) = static_cast<uint8_t>(backgroundColor.b * backgroundColorAlpha);
547 *(packedBackgroundColorBuffer + 1u) = static_cast<uint8_t>(backgroundColor.g * backgroundColorAlpha);
548 *(packedBackgroundColorBuffer) = static_cast<uint8_t>(backgroundColor.r * backgroundColorAlpha);
550 for(int32_t y = yRangeMin; y < yRangeMax; y++)
552 for(int32_t x = xRangeMin; x < xRangeMax; x++)
554 // Note : this is same logic as bitmap[y][x] = backgroundColor;
555 *(bitmapBuffer + x) = packedBackgroundColor;
557 bitmapBuffer += glyphData.width;
562 Devel::PixelBuffer DrawGlyphsBackground(const ViewModel* model, Devel::PixelBuffer& buffer, const uint32_t bufferWidth, const uint32_t bufferHeight, const bool ignoreHorizontalAlignment, const int32_t horizontalOffset, const int32_t verticalOffset)
564 // Retrieve lines, glyphs, positions and colors from the view model.
565 const Length modelNumberOfLines = model->GetNumberOfLines();
566 const LineRun* const modelLinesBuffer = model->GetLines();
567 const Length numberOfGlyphs = model->GetNumberOfGlyphs();
568 const GlyphInfo* const glyphsBuffer = model->GetGlyphs();
569 const Vector2* const positionBuffer = model->GetLayout();
570 const Vector4* const backgroundColorsBuffer = model->GetBackgroundColors();
571 const ColorIndex* const backgroundColorIndicesBuffer = model->GetBackgroundColorIndices();
573 const DevelText::VerticalLineAlignment::Type verLineAlign = model->GetVerticalLineAlignment();
575 // Create and initialize the pixel buffer.
577 glyphData.verticalOffset = verticalOffset;
578 glyphData.width = bufferWidth;
579 glyphData.height = bufferHeight;
580 glyphData.bitmapBuffer = buffer;
581 glyphData.horizontalOffset = 0;
583 ColorIndex prevBackgroundColorIndex = 0;
584 ColorIndex backgroundColorIndex = 0;
586 // Traverses the lines of the text.
587 for(LineIndex lineIndex = 0u; lineIndex < modelNumberOfLines; ++lineIndex)
589 const LineRun& line = *(modelLinesBuffer + lineIndex);
591 // Sets the horizontal offset of the line.
592 glyphData.horizontalOffset = ignoreHorizontalAlignment ? 0 : static_cast<int32_t>(line.alignmentOffset);
593 glyphData.horizontalOffset += horizontalOffset;
595 // Increases the vertical offset with the line's ascender.
596 glyphData.verticalOffset += static_cast<int32_t>(line.ascender + GetPreOffsetVerticalLineAlignment(line, verLineAlign));
598 float left = bufferWidth;
600 float baseline = 0.0f;
602 // Traverses the glyphs of the line.
603 const GlyphIndex endGlyphIndex = std::min(numberOfGlyphs, line.glyphRun.glyphIndex + line.glyphRun.numberOfGlyphs);
604 for(GlyphIndex glyphIndex = line.glyphRun.glyphIndex; glyphIndex < endGlyphIndex; ++glyphIndex)
606 // Retrieve the glyph's info.
607 const GlyphInfo* const glyphInfo = glyphsBuffer + glyphIndex;
609 if((glyphInfo->width < Math::MACHINE_EPSILON_1000) ||
610 (glyphInfo->height < Math::MACHINE_EPSILON_1000))
612 // Nothing to do if default background color, the glyph's width or height is zero.
616 backgroundColorIndex = (nullptr == backgroundColorsBuffer) ? 0u : *(backgroundColorIndicesBuffer + glyphIndex);
618 if((backgroundColorIndex != prevBackgroundColorIndex) &&
619 (prevBackgroundColorIndex != 0u))
621 const Vector4& backgroundColor = *(backgroundColorsBuffer + prevBackgroundColorIndex - 1u);
622 DrawBackgroundColor(backgroundColor, bufferWidth, bufferHeight, glyphData, baseline, line, left, right);
625 if(backgroundColorIndex == 0u)
627 prevBackgroundColorIndex = backgroundColorIndex;
628 //if background color is the default do nothing
632 // Retrieves the glyph's position.
633 const Vector2* const position = positionBuffer + glyphIndex;
635 if(baseline < position->y + glyphInfo->yBearing)
637 baseline = position->y + glyphInfo->yBearing;
640 // Calculate the positions of leftmost and rightmost glyphs in the current line
641 if((position->x < left) || (backgroundColorIndex != prevBackgroundColorIndex))
643 left = position->x - glyphInfo->xBearing;
646 if(position->x + glyphInfo->width > right)
648 right = position->x - glyphInfo->xBearing + glyphInfo->advance;
651 prevBackgroundColorIndex = backgroundColorIndex;
654 //draw last background at line end if not default
655 if(backgroundColorIndex != 0u)
657 const Vector4& backgroundColor = *(backgroundColorsBuffer + backgroundColorIndex - 1u);
658 DrawBackgroundColor(backgroundColor, bufferWidth, bufferHeight, glyphData, baseline, line, left, right);
661 // Increases the vertical offset with the line's descender.
662 glyphData.verticalOffset += static_cast<int32_t>(-line.descender + GetPostOffsetVerticalLineAlignment(line, verLineAlign));
665 return glyphData.bitmapBuffer;
668 /// Draws the specified strikethrough color to the buffer
669 void DrawStrikethrough(const uint32_t bufferWidth,
670 const uint32_t bufferHeight,
671 GlyphData& glyphData,
672 const float baseline,
673 const float strikethroughStartingYPosition,
674 const float maxStrikethroughHeight,
675 const float lineExtentLeft,
676 const float lineExtentRight,
677 const StrikethroughStyleProperties& commonStrikethroughProperties,
678 const StrikethroughStyleProperties& currentStrikethroughProperties,
681 const Vector4& strikethroughColor = currentStrikethroughProperties.colorDefined ? currentStrikethroughProperties.color : commonStrikethroughProperties.color;
683 const uint32_t yRangeMin = static_cast<uint32_t>(strikethroughStartingYPosition);
684 const uint32_t yRangeMax = std::min(bufferHeight, static_cast<uint32_t>(strikethroughStartingYPosition + maxStrikethroughHeight));
685 const uint32_t xRangeMin = static_cast<uint32_t>(glyphData.horizontalOffset + lineExtentLeft);
686 const uint32_t xRangeMax = std::min(bufferWidth, static_cast<uint32_t>(glyphData.horizontalOffset + lineExtentRight + 1)); // Due to include last point, we add 1 here
688 // If current glyph don't need to be rendered, just ignore.
689 if(yRangeMax <= yRangeMin || xRangeMax <= xRangeMin)
694 // We can optimize by memset when strikethroughColor.a is near zero
695 uint8_t strikethroughColorAlpha = static_cast<uint8_t>(strikethroughColor.a * 255.f);
697 uint32_t* bitmapBuffer = reinterpret_cast<uint32_t*>(glyphData.bitmapBuffer.GetBuffer());
699 // Skip yRangeMin line.
700 bitmapBuffer += yRangeMin * glyphData.width;
702 if(strikethroughColorAlpha == 0)
704 for(uint32_t y = yRangeMin; y < yRangeMax; y++)
706 // We can use memset.
707 memset(bitmapBuffer + xRangeMin, 0, (xRangeMax - xRangeMin) * sizeof(uint32_t));
708 bitmapBuffer += glyphData.width;
713 uint32_t packedStrikethroughColor = 0u;
714 uint8_t* packedStrikethroughColorBuffer = reinterpret_cast<uint8_t*>(&packedStrikethroughColor);
716 // Write the color to the pixel buffer
717 *(packedStrikethroughColorBuffer + 3u) = strikethroughColorAlpha;
718 *(packedStrikethroughColorBuffer + 2u) = static_cast<uint8_t>(strikethroughColor.b * strikethroughColorAlpha);
719 *(packedStrikethroughColorBuffer + 1u) = static_cast<uint8_t>(strikethroughColor.g * strikethroughColorAlpha);
720 *(packedStrikethroughColorBuffer) = static_cast<uint8_t>(strikethroughColor.r * strikethroughColorAlpha);
722 for(uint32_t y = yRangeMin; y < yRangeMax; y++)
724 for(uint32_t x = xRangeMin; x < xRangeMax; x++)
726 // Note : this is same logic as bitmap[y][x] = strikethroughColor;
727 *(bitmapBuffer + x) = packedStrikethroughColor;
729 bitmapBuffer += glyphData.width;
735 * @brief Create an initialized image buffer filled with transparent color.
737 * Creates the pixel data used to generate the final image with the given size.
739 * @param[in] bufferWidth The width of the image buffer.
740 * @param[in] bufferHeight The height of the image buffer.
741 * @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).
743 * @return An image buffer.
745 inline Devel::PixelBuffer CreateTransparentImageBuffer(const uint32_t bufferWidth, const uint32_t bufferHeight, const Pixel::Format pixelFormat)
747 Devel::PixelBuffer imageBuffer = Devel::PixelBuffer::New(bufferWidth, bufferHeight, pixelFormat);
749 if(Pixel::RGBA8888 == pixelFormat)
751 const uint32_t bufferSizeInt = bufferWidth * bufferHeight;
752 const size_t bufferSizeChar = sizeof(uint32_t) * static_cast<std::size_t>(bufferSizeInt);
753 memset(imageBuffer.GetBuffer(), 0, bufferSizeChar);
757 memset(imageBuffer.GetBuffer(), 0, static_cast<std::size_t>(bufferWidth * bufferHeight));
764 * @brief Combine the two RGBA image buffers together.
766 * The top layer buffer will blend over the bottom layer buffer:
767 * - If the pixel is not fully opaque from either buffer, it will be blended with
768 * the pixel from the other buffer and copied to the combined buffer.
769 * - If the pixels from both buffers are fully opaque, the pixels from the top layer
770 * buffer will be copied to the combined buffer.
772 * Due to the performance issue, We need to re-use input'ed pixelBuffer memory.
773 * We can determine which pixelBuffer's memory is destination
775 * @param[in, out] topPixelBuffer The top layer buffer.
776 * @param[in, out] bottomPixelBuffer The bottom layer buffer.
777 * @param[in] bufferWidth The width of the image buffer.
778 * @param[in] bufferHeight The height of the image buffer.
779 * @param[in] storeResultIntoTop True if we store the combined image buffer result into topPixelBuffer.
780 * False if we store the combined image buffer result into bottomPixelBuffer.
783 void CombineImageBuffer(Devel::PixelBuffer& __restrict__ topPixelBuffer, Devel::PixelBuffer& __restrict__ bottomPixelBuffer, const uint32_t bufferWidth, const uint32_t bufferHeight, bool storeResultIntoTop)
785 // Assume that we always combine two RGBA images
786 // Jump with 4bytes for optimize runtime.
787 uint32_t* topBuffer = reinterpret_cast<uint32_t*>(topPixelBuffer.GetBuffer());
788 uint32_t* bottomBuffer = reinterpret_cast<uint32_t*>(bottomPixelBuffer.GetBuffer());
790 if(topBuffer == NULL && bottomBuffer == NULL)
792 // Nothing to do if both buffers are empty.
796 if(topBuffer == NULL)
798 // Nothing to do if topBuffer is empty.
799 // If we need to store the result into top, change topPixelBuffer as bottomPixelBuffer.
800 if(storeResultIntoTop)
802 topPixelBuffer = bottomPixelBuffer;
807 if(bottomBuffer == NULL)
809 // Nothing to do if bottomBuffer is empty.
810 // If we need to store the result into bottom, change bottomPixelBuffer as topPixelBuffer.
811 if(!storeResultIntoTop)
813 bottomPixelBuffer = topPixelBuffer;
818 const uint32_t bufferSizeInt = bufferWidth * bufferHeight;
820 uint32_t* __restrict__ combinedBuffer = storeResultIntoTop ? topBuffer : bottomBuffer;
821 uint8_t* __restrict__ topAlphaBufferPointer = reinterpret_cast<uint8_t*>(topBuffer) + 3;
823 for(uint32_t pixelIndex = 0; pixelIndex < bufferSizeInt; ++pixelIndex)
825 // If the alpha of the pixel in either buffer is not fully opaque, blend the two pixels.
826 // Otherwise, copy pixel from topBuffer to combinedBuffer.
827 // Note : Be careful when we read & write into combinedBuffer. It can be write into same pointer.
829 uint8_t topAlpha = *topAlphaBufferPointer;
833 // Copy the pixel from bottomBuffer to combinedBuffer
834 if(storeResultIntoTop)
836 *(combinedBuffer) = *(bottomBuffer);
839 else if(topAlpha == 255)
841 // Copy the pixel from topBuffer to combinedBuffer
842 if(!storeResultIntoTop)
844 *(combinedBuffer) = *(topBuffer);
849 // At least one pixel is not fully opaque
850 // "Over" blend the the pixel from topBuffer with the pixel in bottomBuffer
851 uint32_t blendedBottomBufferColor = *(bottomBuffer);
852 uint8_t* __restrict__ blendedBottomBufferColorBuffer = reinterpret_cast<uint8_t*>(&blendedBottomBufferColor);
854 blendedBottomBufferColorBuffer[0] = MultiplyAndNormalizeColor(blendedBottomBufferColorBuffer[0], 255 - topAlpha);
855 blendedBottomBufferColorBuffer[1] = MultiplyAndNormalizeColor(blendedBottomBufferColorBuffer[1], 255 - topAlpha);
856 blendedBottomBufferColorBuffer[2] = MultiplyAndNormalizeColor(blendedBottomBufferColorBuffer[2], 255 - topAlpha);
857 blendedBottomBufferColorBuffer[3] = MultiplyAndNormalizeColor(blendedBottomBufferColorBuffer[3], 255 - topAlpha);
859 *(combinedBuffer) = *(topBuffer) + blendedBottomBufferColor;
862 // Increase each buffer's pointer.
866 topAlphaBufferPointer += sizeof(uint32_t) / sizeof(uint8_t);
872 TypesetterPtr Typesetter::New(const ModelInterface* const model)
874 return TypesetterPtr(new Typesetter(model));
877 ViewModel* Typesetter::GetViewModel()
882 PixelData Typesetter::Render(const Vector2& size, Toolkit::DevelText::TextDirection::Type textDirection, RenderBehaviour behaviour, bool ignoreHorizontalAlignment, Pixel::Format pixelFormat)
884 // @todo. This initial implementation for a TextLabel has only one visible page.
886 // Elides the text if needed.
887 mModel->ElideGlyphs();
889 // Retrieves the layout size.
890 const Size& layoutSize = mModel->GetLayoutSize();
892 const int32_t outlineWidth = static_cast<int32_t>(mModel->GetOutlineWidth());
894 // Set the offset for the horizontal alignment according to the text direction and outline width.
897 switch(mModel->GetHorizontalAlignment())
899 case HorizontalAlignment::BEGIN:
904 case HorizontalAlignment::CENTER:
906 penX += (textDirection == Toolkit::DevelText::TextDirection::LEFT_TO_RIGHT) ? -outlineWidth : outlineWidth;
909 case HorizontalAlignment::END:
911 penX += (textDirection == Toolkit::DevelText::TextDirection::LEFT_TO_RIGHT) ? -outlineWidth * 2 : outlineWidth * 2;
916 // Set the offset for the vertical alignment.
919 switch(mModel->GetVerticalAlignment())
921 case VerticalAlignment::TOP:
926 case VerticalAlignment::CENTER:
928 penY = static_cast<int32_t>(0.5f * (size.height - layoutSize.height));
929 penY = penY < 0.f ? 0.f : penY;
932 case VerticalAlignment::BOTTOM:
934 penY = static_cast<int32_t>(size.height - layoutSize.height);
939 // Generate the image buffers of the text for each different style first,
940 // then combine all of them together as one final image buffer. We try to
941 // do all of these in CPU only, so that once the final texture is generated,
942 // no calculation is needed in GPU during each frame.
944 const uint32_t bufferWidth = static_cast<uint32_t>(size.width);
945 const uint32_t bufferHeight = static_cast<uint32_t>(size.height);
947 const uint32_t bufferSizeInt = bufferWidth * bufferHeight;
948 const size_t bufferSizeChar = sizeof(uint32_t) * static_cast<std::size_t>(bufferSizeInt);
950 //Elided text in ellipsis at START could start on index greater than 0
951 auto startIndexOfGlyphs = mModel->GetStartIndexOfElidedGlyphs();
952 auto endIndexOfGlyphs = mModel->GetEndIndexOfElidedGlyphs();
954 Devel::PixelBuffer imageBuffer;
956 if(RENDER_MASK == behaviour)
958 // Generate the image buffer as an alpha mask for color glyphs.
959 imageBuffer = CreateImageBuffer(bufferWidth, bufferHeight, Typesetter::STYLE_MASK, ignoreHorizontalAlignment, pixelFormat, penX, penY, startIndexOfGlyphs, endIndexOfGlyphs);
961 else if(RENDER_NO_TEXT == behaviour || RENDER_OVERLAY_STYLE == behaviour)
963 // Generate an empty image buffer so that it can been combined with the image buffers for styles
964 imageBuffer = Devel::PixelBuffer::New(bufferWidth, bufferHeight, Pixel::RGBA8888);
965 memset(imageBuffer.GetBuffer(), 0u, bufferSizeChar);
969 // Generate the image buffer for the text with no style.
970 imageBuffer = CreateImageBuffer(bufferWidth, bufferHeight, Typesetter::STYLE_NONE, ignoreHorizontalAlignment, pixelFormat, penX, penY, startIndexOfGlyphs, endIndexOfGlyphs);
973 if((RENDER_NO_STYLES != behaviour) && (RENDER_MASK != behaviour))
975 // Generate the outline if enabled
976 const uint16_t outlineWidth = mModel->GetOutlineWidth();
977 const float outlineAlpha = mModel->GetOutlineColor().a;
978 if(outlineWidth != 0u && fabsf(outlineAlpha) > Math::MACHINE_EPSILON_1 && RENDER_OVERLAY_STYLE != behaviour)
980 // Create the image buffer for outline
981 Devel::PixelBuffer outlineImageBuffer = CreateImageBuffer(bufferWidth, bufferHeight, Typesetter::STYLE_OUTLINE, ignoreHorizontalAlignment, pixelFormat, penX, penY, startIndexOfGlyphs, endIndexOfGlyphs);
983 // Combine the two buffers
984 CombineImageBuffer(imageBuffer, outlineImageBuffer, bufferWidth, bufferHeight, true);
987 // @todo. Support shadow for partial text later on.
989 // Generate the shadow if enabled
990 const Vector2& shadowOffset = mModel->GetShadowOffset();
991 const float shadowAlpha = mModel->GetShadowColor().a;
992 if(RENDER_OVERLAY_STYLE != behaviour && fabsf(shadowAlpha) > Math::MACHINE_EPSILON_1 && (fabsf(shadowOffset.x) > Math::MACHINE_EPSILON_1 || fabsf(shadowOffset.y) > Math::MACHINE_EPSILON_1))
994 // Create the image buffer for shadow
995 Devel::PixelBuffer shadowImageBuffer = CreateImageBuffer(bufferWidth, bufferHeight, Typesetter::STYLE_SHADOW, ignoreHorizontalAlignment, pixelFormat, penX, penY, startIndexOfGlyphs, endIndexOfGlyphs);
997 // Check whether it will be a soft shadow
998 const float& blurRadius = mModel->GetShadowBlurRadius();
1000 if(blurRadius > Math::MACHINE_EPSILON_1)
1002 shadowImageBuffer.ApplyGaussianBlur(blurRadius);
1005 // Combine the two buffers
1006 CombineImageBuffer(imageBuffer, shadowImageBuffer, bufferWidth, bufferHeight, true);
1009 // Generate the background if enabled
1010 const bool backgroundEnabled = mModel->IsBackgroundEnabled();
1011 const bool backgroundMarkupSet = mModel->IsMarkupBackgroundColorSet();
1012 if((backgroundEnabled || backgroundMarkupSet) && RENDER_OVERLAY_STYLE != behaviour)
1014 Devel::PixelBuffer backgroundImageBuffer;
1016 if(backgroundEnabled)
1018 backgroundImageBuffer = CreateImageBuffer(bufferWidth, bufferHeight, Typesetter::STYLE_BACKGROUND, ignoreHorizontalAlignment, pixelFormat, penX, penY, startIndexOfGlyphs, endIndexOfGlyphs);
1022 backgroundImageBuffer = CreateTransparentImageBuffer(bufferWidth, bufferHeight, pixelFormat);
1025 if(backgroundMarkupSet)
1027 DrawGlyphsBackground(mModel, backgroundImageBuffer, bufferWidth, bufferHeight, ignoreHorizontalAlignment, penX, penY);
1030 // Combine the two buffers
1031 CombineImageBuffer(imageBuffer, backgroundImageBuffer, bufferWidth, bufferHeight, true);
1034 if(RENDER_OVERLAY_STYLE == behaviour)
1036 if(mModel->IsUnderlineEnabled())
1038 // Create the image buffer for underline
1039 Devel::PixelBuffer underlineImageBuffer = CreateImageBuffer(bufferWidth, bufferHeight, Typesetter::STYLE_UNDERLINE, ignoreHorizontalAlignment, pixelFormat, penX, penY, startIndexOfGlyphs, endIndexOfGlyphs);
1041 // Combine the two buffers
1042 CombineImageBuffer(imageBuffer, underlineImageBuffer, bufferWidth, bufferHeight, true);
1045 if(mModel->IsStrikethroughEnabled())
1047 // Create the image buffer for strikethrough
1048 Devel::PixelBuffer strikethroughImageBuffer = CreateImageBuffer(bufferWidth, bufferHeight, Typesetter::STYLE_STRIKETHROUGH, ignoreHorizontalAlignment, pixelFormat, penX, penY, 0u, endIndexOfGlyphs);
1050 // Combine the two buffers
1051 CombineImageBuffer(imageBuffer, strikethroughImageBuffer, bufferWidth, bufferHeight, true);
1054 // Markup-Processor for overlay styles
1055 if(mModel->IsMarkupProcessorEnabled())
1057 if(mModel->IsMarkupUnderlineSet())
1059 imageBuffer = ApplyUnderlineMarkupImageBuffer(imageBuffer, bufferWidth, bufferHeight, ignoreHorizontalAlignment, pixelFormat, penX, penY);
1062 if(mModel->IsMarkupStrikethroughSet())
1064 imageBuffer = ApplyStrikethroughMarkupImageBuffer(imageBuffer, bufferWidth, bufferHeight, ignoreHorizontalAlignment, pixelFormat, penX, penY);
1070 // Create the final PixelData for the combined image buffer
1071 PixelData pixelData = Devel::PixelBuffer::Convert(imageBuffer);
1076 Devel::PixelBuffer Typesetter::CreateImageBuffer(const uint32_t bufferWidth, const uint32_t bufferHeight, const Typesetter::Style style, const bool ignoreHorizontalAlignment, const Pixel::Format pixelFormat, const int32_t horizontalOffset, const int32_t verticalOffset, const GlyphIndex fromGlyphIndex, const GlyphIndex toGlyphIndex)
1078 // Retrieve lines, glyphs, positions and colors from the view model.
1079 const Length modelNumberOfLines = mModel->GetNumberOfLines();
1080 const LineRun* const __restrict__ modelLinesBuffer = mModel->GetLines();
1081 const GlyphInfo* const __restrict__ glyphsBuffer = mModel->GetGlyphs();
1082 const Vector2* const __restrict__ positionBuffer = mModel->GetLayout();
1083 const Vector4* const __restrict__ colorsBuffer = mModel->GetColors();
1084 const ColorIndex* const __restrict__ colorIndexBuffer = mModel->GetColorIndices();
1085 const GlyphInfo* __restrict__ hyphens = mModel->GetHyphens();
1086 const Length* __restrict__ hyphenIndices = mModel->GetHyphenIndices();
1087 const Length hyphensCount = mModel->GetHyphensCount();
1089 // Elided text info. Indices according to elided text and Ellipsis position.
1090 const auto startIndexOfGlyphs = mModel->GetStartIndexOfElidedGlyphs();
1091 const auto endIndexOfGlyphs = mModel->GetEndIndexOfElidedGlyphs();
1092 const auto firstMiddleIndexOfElidedGlyphs = mModel->GetFirstMiddleIndexOfElidedGlyphs();
1093 const auto secondMiddleIndexOfElidedGlyphs = mModel->GetSecondMiddleIndexOfElidedGlyphs();
1094 const auto ellipsisPosition = mModel->GetEllipsisPosition();
1096 // Whether to use the default color.
1097 const bool useDefaultColor = (NULL == colorsBuffer);
1098 const Vector4& defaultColor = mModel->GetDefaultColor();
1100 // Create and initialize the pixel buffer.
1101 GlyphData glyphData;
1102 glyphData.verticalOffset = verticalOffset;
1103 glyphData.width = bufferWidth;
1104 glyphData.height = bufferHeight;
1105 glyphData.bitmapBuffer = CreateTransparentImageBuffer(bufferWidth, bufferHeight, pixelFormat);
1106 glyphData.horizontalOffset = 0;
1108 // Get a handle of the font client. Used to retrieve the bitmaps of the glyphs.
1109 TextAbstraction::FontClient fontClient = TextAbstraction::FontClient::Get();
1110 Length hyphenIndex = 0;
1112 const Character* __restrict__ textBuffer = mModel->GetTextBuffer();
1113 float calculatedAdvance = 0.f;
1114 const Vector<CharacterIndex>& __restrict__ glyphToCharacterMap = mModel->GetGlyphsToCharacters();
1115 const CharacterIndex* __restrict__ glyphToCharacterMapBuffer = glyphToCharacterMap.Begin();
1117 const DevelText::VerticalLineAlignment::Type verLineAlign = mModel->GetVerticalLineAlignment();
1119 // Traverses the lines of the text.
1120 for(LineIndex lineIndex = 0u; lineIndex < modelNumberOfLines; ++lineIndex)
1122 const LineRun& line = *(modelLinesBuffer + lineIndex);
1124 // Sets the horizontal offset of the line.
1125 glyphData.horizontalOffset = ignoreHorizontalAlignment ? 0 : static_cast<int32_t>(line.alignmentOffset);
1126 glyphData.horizontalOffset += horizontalOffset;
1128 // Increases the vertical offset with the line's ascender.
1129 glyphData.verticalOffset += static_cast<int32_t>(line.ascender + GetPreOffsetVerticalLineAlignment(line, verLineAlign));
1131 // Retrieves the glyph's outline width
1132 float outlineWidth = static_cast<float>(mModel->GetOutlineWidth());
1134 if(style == Typesetter::STYLE_OUTLINE)
1136 glyphData.horizontalOffset -= outlineWidth;
1139 // Only need to add the vertical outline offset for the first line
1140 glyphData.verticalOffset -= outlineWidth;
1143 else if(style == Typesetter::STYLE_SHADOW)
1145 const Vector2& shadowOffset = mModel->GetShadowOffset();
1146 glyphData.horizontalOffset += shadowOffset.x - outlineWidth; // if outline enabled then shadow should offset from outline
1150 // Only need to add the vertical shadow offset for first line
1151 glyphData.verticalOffset += shadowOffset.y - outlineWidth;
1155 const bool underlineEnabled = mModel->IsUnderlineEnabled();
1156 const bool strikethroughEnabled = mModel->IsStrikethroughEnabled();
1157 const float modelCharacterSpacing = mModel->GetCharacterSpacing();
1159 // Get the character-spacing runs.
1160 const Vector<CharacterSpacingGlyphRun>& __restrict__ characterSpacingGlyphRuns = mModel->GetCharacterSpacingGlyphRuns();
1162 // Aggregate underline-style-properties from mModel
1163 const UnderlineStyleProperties modelUnderlineProperties{mModel->GetUnderlineType(),
1164 mModel->GetUnderlineColor(),
1165 mModel->GetUnderlineHeight(),
1166 mModel->GetDashedUnderlineGap(),
1167 mModel->GetDashedUnderlineWidth(),
1174 // Aggregate strikethrough-style-properties from mModel
1175 const StrikethroughStyleProperties modelStrikethroughProperties{mModel->GetStrikethroughColor(),
1176 mModel->GetStrikethroughHeight(),
1180 // Get the underline runs.
1181 const Length numberOfUnderlineRuns = mModel->GetNumberOfUnderlineRuns();
1182 Vector<UnderlinedGlyphRun> underlineRuns;
1183 underlineRuns.Resize(numberOfUnderlineRuns);
1184 mModel->GetUnderlineRuns(underlineRuns.Begin(), 0u, numberOfUnderlineRuns);
1186 // Get the strikethrough runs.
1187 const Length numberOfStrikethroughRuns = mModel->GetNumberOfStrikethroughRuns();
1188 Vector<StrikethroughGlyphRun> strikethroughRuns;
1189 strikethroughRuns.Resize(numberOfStrikethroughRuns);
1190 mModel->GetStrikethroughRuns(strikethroughRuns.Begin(), 0u, numberOfStrikethroughRuns);
1192 bool thereAreUnderlinedGlyphs = false;
1193 bool thereAreStrikethroughGlyphs = false;
1195 float currentUnderlinePosition = 0.0f;
1196 float currentUnderlineHeight = modelUnderlineProperties.height;
1197 float maxUnderlineHeight = currentUnderlineHeight;
1198 auto currentUnderlineProperties = modelUnderlineProperties;
1200 float currentStrikethroughHeight = modelStrikethroughProperties.height;
1201 float maxStrikethroughHeight = currentStrikethroughHeight;
1202 auto currentStrikethroughProperties = modelStrikethroughProperties;
1203 float strikethroughStartingYPosition = 0.0f;
1205 FontId lastFontId = 0;
1207 float lineExtentLeft = bufferWidth;
1208 float lineExtentRight = 0.0f;
1209 float baseline = 0.0f;
1210 bool addHyphen = false;
1212 // Traverses the glyphs of the line.
1213 const GlyphIndex startGlyphIndex = std::max(std::max(line.glyphRun.glyphIndex, startIndexOfGlyphs), fromGlyphIndex);
1214 GlyphIndex endGlyphIndex = (line.isSplitToTwoHalves ? line.glyphRunSecondHalf.glyphIndex + line.glyphRunSecondHalf.numberOfGlyphs : line.glyphRun.glyphIndex + line.glyphRun.numberOfGlyphs) - 1u;
1215 endGlyphIndex = std::min(std::min(endGlyphIndex, endIndexOfGlyphs), toGlyphIndex);
1217 for(GlyphIndex glyphIndex = startGlyphIndex; glyphIndex <= endGlyphIndex; ++glyphIndex)
1219 //To handle START case of ellipsis, the first glyph has been shifted
1220 //glyphIndex represent indices in whole glyphs but elidedGlyphIndex represents indices in elided Glyphs
1221 GlyphIndex elidedGlyphIndex = glyphIndex - startIndexOfGlyphs;
1223 //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.
1224 if(ellipsisPosition == DevelText::EllipsisPosition::MIDDLE)
1226 if(glyphIndex > firstMiddleIndexOfElidedGlyphs &&
1227 glyphIndex < secondMiddleIndexOfElidedGlyphs)
1229 // Ignore any glyph that removed for MIDDLE ellipsis
1232 if(glyphIndex >= secondMiddleIndexOfElidedGlyphs)
1234 elidedGlyphIndex -= (secondMiddleIndexOfElidedGlyphs - firstMiddleIndexOfElidedGlyphs - 1u);
1238 // Retrieve the glyph's info.
1239 const GlyphInfo* glyphInfo;
1241 if(addHyphen && hyphens)
1243 glyphInfo = hyphens + hyphenIndex;
1248 glyphInfo = glyphsBuffer + elidedGlyphIndex;
1251 if((glyphInfo->width < Math::MACHINE_EPSILON_1000) ||
1252 (glyphInfo->height < Math::MACHINE_EPSILON_1000))
1254 // Nothing to do if the glyph's width or height is zero.
1258 Vector<UnderlinedGlyphRun>::ConstIterator currentUnderlinedGlyphRunIt = underlineRuns.End();
1259 const bool underlineGlyph = underlineEnabled || IsGlyphUnderlined(glyphIndex, underlineRuns, currentUnderlinedGlyphRunIt);
1260 currentUnderlineProperties = GetCurrentUnderlineProperties(glyphIndex, underlineGlyph, underlineRuns, currentUnderlinedGlyphRunIt, modelUnderlineProperties);
1261 currentUnderlineHeight = currentUnderlineProperties.height;
1262 thereAreUnderlinedGlyphs = thereAreUnderlinedGlyphs || underlineGlyph;
1264 Vector<StrikethroughGlyphRun>::ConstIterator currentStrikethroughGlyphRunIt = strikethroughRuns.End();
1265 const bool strikethroughGlyph = strikethroughEnabled || IsGlyphStrikethrough(glyphIndex, strikethroughRuns, currentStrikethroughGlyphRunIt);
1266 currentStrikethroughProperties = GetCurrentStrikethroughProperties(glyphIndex, strikethroughGlyph, strikethroughRuns, currentStrikethroughGlyphRunIt, modelStrikethroughProperties);
1267 currentStrikethroughHeight = currentStrikethroughProperties.height;
1268 thereAreStrikethroughGlyphs = thereAreStrikethroughGlyphs || strikethroughGlyph;
1270 // Are we still using the same fontId as previous
1271 if((glyphInfo->fontId != lastFontId) && (strikethroughGlyph || underlineGlyph))
1273 // We need to fetch fresh font underline metrics
1274 FontMetrics fontMetrics;
1275 fontClient.GetFontMetrics(glyphInfo->fontId, fontMetrics);
1277 //The currentUnderlinePosition will be used for both Underline and/or Strikethrough
1278 currentUnderlinePosition = FetchUnderlinePositionFromFontMetrics(fontMetrics);
1282 CalcualteUnderlineHeight(fontMetrics, currentUnderlineHeight, maxUnderlineHeight);
1285 if(strikethroughGlyph)
1287 CalcualteStrikethroughHeight(currentStrikethroughHeight, maxStrikethroughHeight);
1290 // Update lastFontId because fontId is changed
1291 lastFontId = glyphInfo->fontId; // Prevents searching for existing blocksizes when string of the same fontId.
1294 // Retrieves the glyph's position.
1295 Vector2 position = *(positionBuffer + elidedGlyphIndex);
1299 GlyphInfo tempInfo = *(glyphsBuffer + elidedGlyphIndex);
1300 const float characterSpacing = GetGlyphCharacterSpacing(glyphIndex, characterSpacingGlyphRuns, modelCharacterSpacing);
1301 calculatedAdvance = GetCalculatedAdvance(*(textBuffer + (*(glyphToCharacterMapBuffer + elidedGlyphIndex))), characterSpacing, tempInfo.advance);
1302 position.x = position.x + calculatedAdvance - tempInfo.xBearing + glyphInfo->xBearing;
1303 position.y = -glyphInfo->yBearing;
1306 if(baseline < position.y + glyphInfo->yBearing)
1308 baseline = position.y + glyphInfo->yBearing;
1311 // Calculate the positions of leftmost and rightmost glyphs in the current line
1312 if(position.x < lineExtentLeft)
1314 lineExtentLeft = position.x;
1317 if(position.x + glyphInfo->width > lineExtentRight)
1319 lineExtentRight = position.x + glyphInfo->width;
1322 // Retrieves the glyph's color.
1323 const ColorIndex colorIndex = useDefaultColor ? 0u : *(colorIndexBuffer + glyphIndex);
1326 if(style == Typesetter::STYLE_SHADOW)
1328 color = mModel->GetShadowColor();
1330 else if(style == Typesetter::STYLE_OUTLINE)
1332 color = mModel->GetOutlineColor();
1336 color = (useDefaultColor || (0u == colorIndex)) ? defaultColor : *(colorsBuffer + (colorIndex - 1u));
1339 // Premultiply alpha
1344 // Retrieves the glyph's bitmap.
1345 glyphData.glyphBitmap.buffer = NULL;
1346 glyphData.glyphBitmap.width = glyphInfo->width; // Desired width and height.
1347 glyphData.glyphBitmap.height = glyphInfo->height;
1349 if(style != Typesetter::STYLE_OUTLINE && style != Typesetter::STYLE_SHADOW)
1351 // Don't render outline for other styles
1352 outlineWidth = 0.0f;
1355 if(style != Typesetter::STYLE_UNDERLINE && style != Typesetter::STYLE_STRIKETHROUGH)
1357 fontClient.CreateBitmap(glyphInfo->fontId,
1359 glyphInfo->isItalicRequired,
1360 glyphInfo->isBoldRequired,
1361 glyphData.glyphBitmap,
1362 static_cast<int32_t>(outlineWidth));
1365 // Sets the glyph's bitmap into the bitmap of the whole text.
1366 if(NULL != glyphData.glyphBitmap.buffer)
1368 if(style == Typesetter::STYLE_OUTLINE)
1370 // Set the position offset for the current glyph
1371 glyphData.horizontalOffset -= glyphData.glyphBitmap.outlineOffsetX;
1372 glyphData.verticalOffset -= glyphData.glyphBitmap.outlineOffsetY;
1375 // Set the buffer of the glyph's bitmap into the final bitmap's buffer
1376 TypesetGlyph(glyphData,
1382 if(style == Typesetter::STYLE_OUTLINE)
1384 // Reset the position offset for the next glyph
1385 glyphData.horizontalOffset += glyphData.glyphBitmap.outlineOffsetX;
1386 glyphData.verticalOffset += glyphData.glyphBitmap.outlineOffsetY;
1389 // free the glyphBitmap.buffer if it is owner of buffer
1390 if(glyphData.glyphBitmap.isBufferOwned)
1392 free(glyphData.glyphBitmap.buffer);
1393 glyphData.glyphBitmap.isBufferOwned = false;
1395 glyphData.glyphBitmap.buffer = NULL;
1400 while((hyphenIndex < hyphensCount) && (glyphIndex > hyphenIndices[hyphenIndex]))
1405 addHyphen = ((hyphenIndex < hyphensCount) && ((glyphIndex + 1) == hyphenIndices[hyphenIndex]));
1413 // Draw the underline from the leftmost glyph to the rightmost glyph
1414 if(thereAreUnderlinedGlyphs && style == Typesetter::STYLE_UNDERLINE)
1416 DrawUnderline(bufferWidth, bufferHeight, glyphData, baseline, currentUnderlinePosition, maxUnderlineHeight, lineExtentLeft, lineExtentRight, modelUnderlineProperties, currentUnderlineProperties, line);
1419 // Draw the background color from the leftmost glyph to the rightmost glyph
1420 if(style == Typesetter::STYLE_BACKGROUND)
1422 DrawBackgroundColor(mModel->GetBackgroundColor(), bufferWidth, bufferHeight, glyphData, baseline, line, lineExtentLeft, lineExtentRight);
1425 // Draw the strikethrough from the leftmost glyph to the rightmost glyph
1426 if(thereAreStrikethroughGlyphs && style == Typesetter::STYLE_STRIKETHROUGH)
1428 //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.
1429 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.
1430 DrawStrikethrough(bufferWidth, bufferHeight, glyphData, baseline, strikethroughStartingYPosition, maxStrikethroughHeight, lineExtentLeft, lineExtentRight, modelStrikethroughProperties, currentStrikethroughProperties, line);
1433 // Increases the vertical offset with the line's descender & line spacing.
1434 glyphData.verticalOffset += static_cast<int32_t>(-line.descender + GetPostOffsetVerticalLineAlignment(line, verLineAlign));
1437 return glyphData.bitmapBuffer;
1440 Devel::PixelBuffer Typesetter::ApplyUnderlineMarkupImageBuffer(Devel::PixelBuffer topPixelBuffer, const uint32_t bufferWidth, const uint32_t bufferHeight, const bool ignoreHorizontalAlignment, const Pixel::Format pixelFormat, const int32_t horizontalOffset, const int32_t verticalOffset)
1442 // Underline-tags (this is for Markup case)
1443 // Get the underline runs.
1444 const Length numberOfUnderlineRuns = mModel->GetNumberOfUnderlineRuns();
1445 Vector<UnderlinedGlyphRun> underlineRuns;
1446 underlineRuns.Resize(numberOfUnderlineRuns);
1447 mModel->GetUnderlineRuns(underlineRuns.Begin(), 0u, numberOfUnderlineRuns);
1449 // Iterate on the consecutive underlined glyph run and connect them into one chunk of underlined characters.
1450 Vector<UnderlinedGlyphRun>::ConstIterator itGlyphRun = underlineRuns.Begin();
1451 Vector<UnderlinedGlyphRun>::ConstIterator endItGlyphRun = underlineRuns.End();
1452 GlyphIndex startGlyphIndex, endGlyphIndex;
1454 //The outer loop to iterate on the separated chunks of underlined glyph runs
1455 while(itGlyphRun != endItGlyphRun)
1457 startGlyphIndex = itGlyphRun->glyphRun.glyphIndex;
1458 endGlyphIndex = startGlyphIndex + itGlyphRun->glyphRun.numberOfGlyphs - 1;
1460 // Create the image buffer for underline
1461 Devel::PixelBuffer underlineImageBuffer = CreateImageBuffer(bufferWidth, bufferHeight, Typesetter::STYLE_UNDERLINE, ignoreHorizontalAlignment, pixelFormat, horizontalOffset, verticalOffset, startGlyphIndex, endGlyphIndex);
1462 // Combine the two buffers
1463 // Result pixel buffer will be stored into topPixelBuffer.
1464 CombineImageBuffer(underlineImageBuffer, topPixelBuffer, bufferWidth, bufferHeight, false);
1469 return topPixelBuffer;
1472 Devel::PixelBuffer Typesetter::ApplyStrikethroughMarkupImageBuffer(Devel::PixelBuffer topPixelBuffer, const uint32_t bufferWidth, const uint32_t bufferHeight, const bool ignoreHorizontalAlignment, const Pixel::Format pixelFormat, const int32_t horizontalOffset, const int32_t verticalOffset)
1474 // strikethrough-tags (this is for Markup case)
1475 // Get the strikethrough runs.
1476 const Length numberOfStrikethroughRuns = mModel->GetNumberOfStrikethroughRuns();
1477 Vector<StrikethroughGlyphRun> strikethroughRuns;
1478 strikethroughRuns.Resize(numberOfStrikethroughRuns);
1479 mModel->GetStrikethroughRuns(strikethroughRuns.Begin(), 0u, numberOfStrikethroughRuns);
1481 // Iterate on the consecutive strikethrough glyph run and connect them into one chunk of strikethrough characters.
1482 Vector<StrikethroughGlyphRun>::ConstIterator itGlyphRun = strikethroughRuns.Begin();
1483 Vector<StrikethroughGlyphRun>::ConstIterator endItGlyphRun = strikethroughRuns.End();
1484 GlyphIndex startGlyphIndex, endGlyphIndex;
1486 //The outer loop to iterate on the separated chunks of strikethrough glyph runs
1487 while(itGlyphRun != endItGlyphRun)
1489 startGlyphIndex = itGlyphRun->glyphRun.glyphIndex;
1490 endGlyphIndex = startGlyphIndex + itGlyphRun->glyphRun.numberOfGlyphs - 1;
1492 // Create the image buffer for strikethrough
1493 Devel::PixelBuffer strikethroughImageBuffer = CreateImageBuffer(bufferWidth, bufferHeight, Typesetter::STYLE_STRIKETHROUGH, ignoreHorizontalAlignment, pixelFormat, horizontalOffset, verticalOffset, startGlyphIndex, endGlyphIndex);
1494 // Combine the two buffers
1495 // Result pixel buffer will be stored into topPixelBuffer.
1496 CombineImageBuffer(strikethroughImageBuffer, topPixelBuffer, bufferWidth, bufferHeight, false);
1501 return topPixelBuffer;
1504 Typesetter::Typesetter(const ModelInterface* const model)
1505 : mModel(new ViewModel(model))
1509 Typesetter::~Typesetter()
1516 } // namespace Toolkit