From: Eunki, Hong Date: Mon, 2 Sep 2024 10:54:38 +0000 (+0900) Subject: Seperate TypeSetter::CreateImageBuffer functionally X-Git-Tag: dali_2.3.43~14^2 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=0a49d5f7a63bc04a25f130b698cd0955c5f702a3;p=platform%2Fcore%2Fuifw%2Fdali-toolkit.git Seperate TypeSetter::CreateImageBuffer functionally Let we seperate the logic CreateImageBuffer for each LineRun, and for each GlyphInfo. It will reduce CyclomaticComplexity score Change-Id: I36fa65255b64dd48f6ef8917bccbc3f1d9d9b775 Signed-off-by: Eunki, Hong --- diff --git a/dali-toolkit/internal/file.list b/dali-toolkit/internal/file.list index 4464dbb594..59d4955726 100644 --- a/dali-toolkit/internal/file.list +++ b/dali-toolkit/internal/file.list @@ -243,6 +243,7 @@ SET( toolkit_src_files ${toolkit_src_dir}/text/rendering/atlas/atlas-mesh-factory.cpp ${toolkit_src_dir}/text/rendering/text-backend-impl.cpp ${toolkit_src_dir}/text/rendering/text-typesetter.cpp + ${toolkit_src_dir}/text/rendering/text-typesetter-impl.cpp ${toolkit_src_dir}/text/rendering/view-model.cpp ${toolkit_src_dir}/text/rendering/styles/underline-helper-functions.cpp ${toolkit_src_dir}/text/rendering/styles/strikethrough-helper-functions.cpp diff --git a/dali-toolkit/internal/text/rendering/text-typesetter-impl.cpp b/dali-toolkit/internal/text/rendering/text-typesetter-impl.cpp new file mode 100644 index 0000000000..fcf2c56fbb --- /dev/null +++ b/dali-toolkit/internal/text/rendering/text-typesetter-impl.cpp @@ -0,0 +1,1409 @@ +/* + * Copyright (c) 2024 Samsung Electronics Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +// CLASS HEADER +#include + +// EXTERNAL INCLUDES +#include +#include +#include +#include +#include +#include +#include + +// INTERNAL INCLUDES +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Dali +{ +namespace Toolkit +{ +namespace Text +{ +namespace +{ +DALI_INIT_TRACE_FILTER(gTraceFilter, DALI_TRACE_TEXT_PERFORMANCE_MARKER, false); + +const float HALF(0.5f); +const float ONE_AND_A_HALF(1.5f); + +/** + * @brief Fast multiply & divide by 255. It wiil be useful when we applying alpha value in color + * + * @param x The value between [0..255] + * @param y The value between [0..255] + * @return (x*y)/255 + */ +inline uint8_t MultiplyAndNormalizeColor(const uint8_t x, const uint8_t y) noexcept +{ + const uint32_t xy = static_cast(x) * y; + return ((xy << 15) + (xy << 7) + xy) >> 23; +} + +/// Helper macro define for glyph typesetter. It will reduce some duplicated code line. +// clang-format off +/** + * @brief Prepare decode glyph bitmap data. It must be call END_GLYPH_BITMAP end of same scope. + */ +#define BEGIN_GLYPH_BITMAP(data) \ +{ \ + uint32_t glyphOffet = 0u; \ + const bool useLocalScanline = data.glyphBitmap.compressionType != TextAbstraction::GlyphBufferData::CompressionType::NO_COMPRESSION; \ + uint8_t* __restrict__ glyphScanline = useLocalScanline ? (uint8_t*)malloc(data.glyphBitmap.width * glyphPixelSize) : data.glyphBitmap.buffer; \ + DALI_ASSERT_ALWAYS(glyphScanline && "Glyph scanline for buffer is nullptr!"); + +/** + * @brief Macro to skip useless line fast. + */ +#define SKIP_GLYPH_SCANLINE(skipLine) \ +if(useLocalScanline) \ +{ \ + for(int32_t lineIndex = 0; lineIndex < skipLine; ++lineIndex) \ + { \ + TextAbstraction::GlyphBufferData::DecompressScanline(data.glyphBitmap, glyphScanline, glyphOffet); \ + } \ +} \ +else \ +{ \ + glyphScanline += skipLine * static_cast(data.glyphBitmap.width * glyphPixelSize); \ +} + +/** + * @brief Prepare scanline of glyph bitmap data per each lines. It must be call END_GLYPH_SCANLINE_DECODE end of same scope. + */ +#define BEGIN_GLYPH_SCANLINE_DECODE(data) \ +{ \ + if(useLocalScanline) \ + { \ + TextAbstraction::GlyphBufferData::DecompressScanline(data.glyphBitmap, glyphScanline, glyphOffet); \ + } + +/** + * @brief Finalize scanline of glyph bitmap data per each lines. + */ +#define END_GLYPH_SCANLINE_DECODE(data) \ + if(!useLocalScanline) \ + { \ + glyphScanline += data.glyphBitmap.width * glyphPixelSize; \ + } \ +} // For ensure that we call BEGIN_GLYPH_SCANLINE_DECODE before + +/** + * @brief Finalize decode glyph bitmap data. + */ +#define END_GLYPH_BITMAP() \ + if(useLocalScanline) \ + { \ + free(glyphScanline); \ + } \ +} // For ensure that we call BEGIN_GLYPH_BITMAP before + +// clang-format on +/// Helper macro define end. + +/** + * @brief Data struct used to set the buffer of the glyph's bitmap into the final bitmap's buffer. + */ +struct GlyphData +{ + Devel::PixelBuffer bitmapBuffer; ///< The buffer of the whole bitmap. The format is RGBA8888. + Vector2* position; ///< The position of the glyph. + TextAbstraction::GlyphBufferData glyphBitmap; ///< The glyph's bitmap. + uint32_t width; ///< The bitmap's width. + uint32_t height; ///< The bitmap's height. + int32_t horizontalOffset; ///< The horizontal offset to be added to the 'x' glyph's position. + int32_t verticalOffset; ///< The vertical offset to be added to the 'y' glyph's position. +}; + +/** + * @brief Sets the glyph's buffer into the bitmap's buffer. + * + * @param[in, out] data Struct which contains the glyph's data and the bitmap's data. + * @param[in] position The position of the glyph. + * @param[in] color The color of the glyph. + * @param[in] style The style of the text. + * @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). + */ +void TypesetGlyph(GlyphData& __restrict__ data, + const Vector2* const __restrict__ position, + const Vector4* const __restrict__ color, + const Typesetter::Style style, + const Pixel::Format pixelFormat) +{ + if((0u == data.glyphBitmap.width) || (0u == data.glyphBitmap.height)) + { + // Nothing to do if the width or height of the buffer is zero. + return; + } + + // Initial vertical / horizontal offset. + const int32_t yOffset = data.verticalOffset + position->y; + const int32_t xOffset = data.horizontalOffset + position->x; + + // Whether the given glyph is a color one. + const bool isColorGlyph = data.glyphBitmap.isColorEmoji || data.glyphBitmap.isColorBitmap; + const uint32_t glyphPixelSize = Pixel::GetBytesPerPixel(data.glyphBitmap.format); + const uint32_t glyphAlphaIndex = (glyphPixelSize > 0u) ? glyphPixelSize - 1u : 0u; + + // Determinate iterator range. + const int32_t lineIndexRangeMin = std::max(0, -yOffset); + const int32_t lineIndexRangeMax = std::min(static_cast(data.glyphBitmap.height), static_cast(data.height) - yOffset); + const int32_t indexRangeMin = std::max(0, -xOffset); + const int32_t indexRangeMax = std::min(static_cast(data.glyphBitmap.width), static_cast(data.width) - xOffset); + + // If current glyph don't need to be rendered, just ignore. + if(lineIndexRangeMax <= lineIndexRangeMin || indexRangeMax <= indexRangeMin) + { + return; + } + + if(Pixel::RGBA8888 == pixelFormat) + { + uint32_t* __restrict__ bitmapBuffer = reinterpret_cast(data.bitmapBuffer.GetBuffer()); + // Skip basic line. + bitmapBuffer += (lineIndexRangeMin + yOffset) * static_cast(data.width); + + // Fast-cut if style is MASK or OUTLINE. Outline not shown for color glyph. + // Just overwrite transparent color and return. + if(isColorGlyph && (Typesetter::STYLE_MASK == style || Typesetter::STYLE_OUTLINE == style)) + { + for(int32_t lineIndex = lineIndexRangeMin; lineIndex < lineIndexRangeMax; ++lineIndex) + { + // We can use memset here. + memset(bitmapBuffer + xOffset + indexRangeMin, 0, (indexRangeMax - indexRangeMin) * sizeof(uint32_t)); + bitmapBuffer += data.width; + } + return; + } + + const bool swapChannelsBR = Pixel::BGRA8888 == data.glyphBitmap.format; + + // Precalculate input color's packed result. + uint32_t packedInputColor = 0u; + uint8_t* __restrict__ packedInputColorBuffer = reinterpret_cast(&packedInputColor); + + *(packedInputColorBuffer + 3u) = static_cast(color->a * 255); + *(packedInputColorBuffer + 2u) = static_cast(color->b * 255); + *(packedInputColorBuffer + 1u) = static_cast(color->g * 255); + *(packedInputColorBuffer) = static_cast(color->r * 255); + + // Prepare glyph bitmap + BEGIN_GLYPH_BITMAP(data); + + // Skip basic line of glyph. + SKIP_GLYPH_SCANLINE(lineIndexRangeMin); + + // Traverse the pixels of the glyph line per line. + if(isColorGlyph) + { + for(int32_t lineIndex = lineIndexRangeMin; lineIndex < lineIndexRangeMax; ++lineIndex) + { + BEGIN_GLYPH_SCANLINE_DECODE(data); + + for(int32_t index = indexRangeMin; index < indexRangeMax; ++index) + { + const int32_t xOffsetIndex = xOffset + index; + + // Retrieves the color from the color glyph. + uint32_t packedColorGlyph = *(reinterpret_cast(glyphScanline + (index << 2))); + uint8_t* __restrict__ packedColorGlyphBuffer = reinterpret_cast(&packedColorGlyph); + + // Update the alpha channel. + const uint8_t colorAlpha = MultiplyAndNormalizeColor(*(packedInputColorBuffer + 3u), *(packedColorGlyphBuffer + 3u)); + *(packedColorGlyphBuffer + 3u) = colorAlpha; + + if(Typesetter::STYLE_SHADOW == style) + { + // The shadow of color glyph needs to have the shadow color. + *(packedColorGlyphBuffer + 2u) = MultiplyAndNormalizeColor(*(packedInputColorBuffer + 2u), colorAlpha); + *(packedColorGlyphBuffer + 1u) = MultiplyAndNormalizeColor(*(packedInputColorBuffer + 1u), colorAlpha); + *packedColorGlyphBuffer = MultiplyAndNormalizeColor(*packedInputColorBuffer, colorAlpha); + } + else + { + if(swapChannelsBR) + { + std::swap(*packedColorGlyphBuffer, *(packedColorGlyphBuffer + 2u)); // Swap B and R. + } + + *(packedColorGlyphBuffer + 2u) = MultiplyAndNormalizeColor(*(packedColorGlyphBuffer + 2u), colorAlpha); + *(packedColorGlyphBuffer + 1u) = MultiplyAndNormalizeColor(*(packedColorGlyphBuffer + 1u), colorAlpha); + *packedColorGlyphBuffer = MultiplyAndNormalizeColor(*packedColorGlyphBuffer, colorAlpha); + + if(data.glyphBitmap.isColorBitmap) + { + *(packedColorGlyphBuffer + 2u) = MultiplyAndNormalizeColor(*(packedInputColorBuffer + 2u), *(packedColorGlyphBuffer + 2u)); + *(packedColorGlyphBuffer + 1u) = MultiplyAndNormalizeColor(*(packedInputColorBuffer + 1u), *(packedColorGlyphBuffer + 1u)); + *packedColorGlyphBuffer = MultiplyAndNormalizeColor(*packedInputColorBuffer, *packedColorGlyphBuffer); + } + } + + // Set the color into the final pixel buffer. + *(bitmapBuffer + xOffsetIndex) = packedColorGlyph; + } + + bitmapBuffer += data.width; + + END_GLYPH_SCANLINE_DECODE(data); + } + } + else + { + for(int32_t lineIndex = lineIndexRangeMin; lineIndex < lineIndexRangeMax; ++lineIndex) + { + BEGIN_GLYPH_SCANLINE_DECODE(data); + + for(int32_t index = indexRangeMin; index < indexRangeMax; ++index) + { + // Update the alpha channel. + const uint8_t alpha = *(glyphScanline + index * glyphPixelSize + glyphAlphaIndex); + + // Copy non-transparent pixels only + if(alpha > 0u) + { + const int32_t xOffsetIndex = xOffset + index; + + // Check alpha of overlapped pixels + uint32_t& currentColor = *(bitmapBuffer + xOffsetIndex); + uint8_t* packedCurrentColorBuffer = reinterpret_cast(¤tColor); + + // For any pixel overlapped with the pixel in previous glyphs, make sure we don't + // overwrite a previous bigger alpha with a smaller alpha (in order to avoid + // semi-transparent gaps between joint glyphs with overlapped pixels, which could + // happen, for example, in the RTL text when we copy glyphs from right to left). + uint8_t currentAlpha = *(packedCurrentColorBuffer + 3u); + currentAlpha = std::max(currentAlpha, alpha); + if(currentAlpha == 255) + { + // Fast-cut to avoid float type operation. + currentColor = packedInputColor; + } + else + { + // Pack the given color into a 32bit buffer. The alpha channel will be updated later for each pixel. + // The format is RGBA8888. + uint32_t packedColor = 0u; + uint8_t* __restrict__ packedColorBuffer = reinterpret_cast(&packedColor); + + // Color is pre-muliplied with its alpha. + *(packedColorBuffer + 3u) = MultiplyAndNormalizeColor(*(packedInputColorBuffer + 3u), currentAlpha); + *(packedColorBuffer + 2u) = MultiplyAndNormalizeColor(*(packedInputColorBuffer + 2u), currentAlpha); + *(packedColorBuffer + 1u) = MultiplyAndNormalizeColor(*(packedInputColorBuffer + 1u), currentAlpha); + *(packedColorBuffer) = MultiplyAndNormalizeColor(*packedInputColorBuffer, currentAlpha); + + // Set the color into the final pixel buffer. + currentColor = packedColor; + } + } + } + + bitmapBuffer += data.width; + + END_GLYPH_SCANLINE_DECODE(data); + } + } + + END_GLYPH_BITMAP(); + } + else // Pixel::L8 + { + // Below codes required only if not color glyph. + if(!isColorGlyph) + { + uint8_t* __restrict__ bitmapBuffer = data.bitmapBuffer.GetBuffer(); + // Skip basic line. + bitmapBuffer += (lineIndexRangeMin + yOffset) * static_cast(data.width); + + // Prepare glyph bitmap + BEGIN_GLYPH_BITMAP(data); + + // Skip basic line of glyph. + SKIP_GLYPH_SCANLINE(lineIndexRangeMin); + + // Traverse the pixels of the glyph line per line. + for(int32_t lineIndex = lineIndexRangeMin; lineIndex < lineIndexRangeMax; ++lineIndex) + { + BEGIN_GLYPH_SCANLINE_DECODE(data); + + for(int32_t index = indexRangeMin; index < indexRangeMax; ++index) + { + const int32_t xOffsetIndex = xOffset + index; + + // Update the alpha channel. + const uint8_t alpha = *(glyphScanline + index * glyphPixelSize + glyphAlphaIndex); + + // Copy non-transparent pixels only + if(alpha > 0u) + { + // Check alpha of overlapped pixels + uint8_t& currentAlpha = *(bitmapBuffer + xOffsetIndex); + + // For any pixel overlapped with the pixel in previous glyphs, make sure we don't + // overwrite a previous bigger alpha with a smaller alpha (in order to avoid + // semi-transparent gaps between joint glyphs with overlapped pixels, which could + // happen, for example, in the RTL text when we copy glyphs from right to left). + currentAlpha = std::max(currentAlpha, alpha); + } + } + + bitmapBuffer += data.width; + + END_GLYPH_SCANLINE_DECODE(data); + } + + END_GLYPH_BITMAP(); + } + } +} + +/// Draws the background color to the buffer +void DrawBackgroundColor( + Vector4 backgroundColor, + const uint32_t bufferWidth, + const uint32_t bufferHeight, + GlyphData& glyphData, + const float baseline, + const LineRun& line, + const float lineExtentLeft, + const float lineExtentRight) +{ + const int32_t yRangeMin = std::max(0, static_cast(glyphData.verticalOffset + baseline - line.ascender)); + const int32_t yRangeMax = std::min(static_cast(bufferHeight), static_cast(glyphData.verticalOffset + baseline - line.descender)); + const int32_t xRangeMin = std::max(0, static_cast(glyphData.horizontalOffset + lineExtentLeft)); + const int32_t xRangeMax = std::min(static_cast(bufferWidth), static_cast(glyphData.horizontalOffset + lineExtentRight + 1)); // Due to include last point, we add 1 here + + // If current glyph don't need to be rendered, just ignore. + if(yRangeMax <= yRangeMin || xRangeMax <= xRangeMin) + { + return; + } + + // We can optimize by memset when backgroundColor.a is near zero + uint8_t backgroundColorAlpha = static_cast(backgroundColor.a * 255.f); + + uint32_t* bitmapBuffer = reinterpret_cast(glyphData.bitmapBuffer.GetBuffer()); + + // Skip yRangeMin line. + bitmapBuffer += yRangeMin * glyphData.width; + + if(backgroundColorAlpha == 0) + { + for(int32_t y = yRangeMin; y < yRangeMax; y++) + { + // We can use memset. + memset(bitmapBuffer + xRangeMin, 0, (xRangeMax - xRangeMin) * sizeof(uint32_t)); + bitmapBuffer += glyphData.width; + } + } + else + { + uint32_t packedBackgroundColor = 0u; + uint8_t* packedBackgroundColorBuffer = reinterpret_cast(&packedBackgroundColor); + + // Write the color to the pixel buffer + *(packedBackgroundColorBuffer + 3u) = backgroundColorAlpha; + *(packedBackgroundColorBuffer + 2u) = static_cast(backgroundColor.b * backgroundColorAlpha); + *(packedBackgroundColorBuffer + 1u) = static_cast(backgroundColor.g * backgroundColorAlpha); + *(packedBackgroundColorBuffer) = static_cast(backgroundColor.r * backgroundColorAlpha); + + for(int32_t y = yRangeMin; y < yRangeMax; y++) + { + for(int32_t x = xRangeMin; x < xRangeMax; x++) + { + // Note : this is same logic as bitmap[y][x] = backgroundColor; + *(bitmapBuffer + x) = packedBackgroundColor; + } + bitmapBuffer += glyphData.width; + } + } +} + +/// Draws the specified underline color to the buffer +void DrawUnderline( + const uint32_t bufferWidth, + const uint32_t bufferHeight, + GlyphData& glyphData, + const float baseline, + const float currentUnderlinePosition, + const float maxUnderlineHeight, + const float lineExtentLeft, + const float lineExtentRight, + const UnderlineStyleProperties& commonUnderlineProperties, + const UnderlineStyleProperties& currentUnderlineProperties, + const LineRun& line) +{ + const Vector4& underlineColor = currentUnderlineProperties.colorDefined ? currentUnderlineProperties.color : commonUnderlineProperties.color; + const Text::Underline::Type underlineType = currentUnderlineProperties.typeDefined ? currentUnderlineProperties.type : commonUnderlineProperties.type; + const float dashedUnderlineWidth = currentUnderlineProperties.dashWidthDefined ? currentUnderlineProperties.dashWidth : commonUnderlineProperties.dashWidth; + const float dashedUnderlineGap = currentUnderlineProperties.dashGapDefined ? currentUnderlineProperties.dashGap : commonUnderlineProperties.dashGap; + + int32_t underlineYOffset = glyphData.verticalOffset + baseline + currentUnderlinePosition; + + const uint32_t yRangeMin = underlineYOffset; + const uint32_t yRangeMax = std::min(bufferHeight, underlineYOffset + static_cast(maxUnderlineHeight)); + const uint32_t xRangeMin = static_cast(glyphData.horizontalOffset + lineExtentLeft); + const uint32_t xRangeMax = std::min(bufferWidth, static_cast(glyphData.horizontalOffset + lineExtentRight + 1)); // Due to include last point, we add 1 here + + // If current glyph don't need to be rendered, just ignore. + if((underlineType != Text::Underline::DOUBLE && yRangeMax <= yRangeMin) || xRangeMax <= xRangeMin) + { + return; + } + + // We can optimize by memset when underlineColor.a is near zero + uint8_t underlineColorAlpha = static_cast(underlineColor.a * 255.f); + + uint32_t* bitmapBuffer = reinterpret_cast(glyphData.bitmapBuffer.GetBuffer()); + + // Skip yRangeMin line. + bitmapBuffer += yRangeMin * glyphData.width; + + // Note if underlineType is DASHED, we cannot setup color by memset. + if(underlineType != Text::Underline::DASHED && underlineColorAlpha == 0) + { + for(uint32_t y = yRangeMin; y < yRangeMax; y++) + { + // We can use memset. + memset(bitmapBuffer + xRangeMin, 0, (xRangeMax - xRangeMin) * sizeof(uint32_t)); + bitmapBuffer += glyphData.width; + } + if(underlineType == Text::Underline::DOUBLE) + { + int32_t secondUnderlineYOffset = underlineYOffset - ONE_AND_A_HALF * maxUnderlineHeight; + const uint32_t secondYRangeMin = static_cast(std::max(0, secondUnderlineYOffset)); + const uint32_t secondYRangeMax = static_cast(std::max(0, std::min(static_cast(bufferHeight), secondUnderlineYOffset + static_cast(maxUnderlineHeight)))); + + // Rewind bitmapBuffer pointer, and skip secondYRangeMin line. + bitmapBuffer = reinterpret_cast(glyphData.bitmapBuffer.GetBuffer()) + yRangeMin * glyphData.width; + + for(uint32_t y = secondYRangeMin; y < secondYRangeMax; y++) + { + // We can use memset. + memset(bitmapBuffer + xRangeMin, 0, (xRangeMax - xRangeMin) * sizeof(uint32_t)); + bitmapBuffer += glyphData.width; + } + } + } + else + { + uint32_t packedUnderlineColor = 0u; + uint8_t* packedUnderlineColorBuffer = reinterpret_cast(&packedUnderlineColor); + + // Write the color to the pixel buffer + *(packedUnderlineColorBuffer + 3u) = underlineColorAlpha; + *(packedUnderlineColorBuffer + 2u) = static_cast(underlineColor.b * underlineColorAlpha); + *(packedUnderlineColorBuffer + 1u) = static_cast(underlineColor.g * underlineColorAlpha); + *(packedUnderlineColorBuffer) = static_cast(underlineColor.r * underlineColorAlpha); + + for(uint32_t y = yRangeMin; y < yRangeMax; y++) + { + if(underlineType == Text::Underline::DASHED) + { + float dashWidth = dashedUnderlineWidth; + float dashGap = 0; + + for(uint32_t x = xRangeMin; x < xRangeMax; x++) + { + if(Dali::EqualsZero(dashGap) && dashWidth > 0) + { + // Note : this is same logic as bitmap[y][x] = underlineColor; + *(bitmapBuffer + x) = packedUnderlineColor; + dashWidth--; + } + else if(dashGap < dashedUnderlineGap) + { + dashGap++; + } + else + { + //reset + dashWidth = dashedUnderlineWidth; + dashGap = 0; + } + } + } + else + { + for(uint32_t x = xRangeMin; x < xRangeMax; x++) + { + // Note : this is same logic as bitmap[y][x] = underlineColor; + *(bitmapBuffer + x) = packedUnderlineColor; + } + } + bitmapBuffer += glyphData.width; + } + if(underlineType == Text::Underline::DOUBLE) + { + int32_t secondUnderlineYOffset = underlineYOffset - ONE_AND_A_HALF * maxUnderlineHeight; + const uint32_t secondYRangeMin = static_cast(std::max(0, secondUnderlineYOffset)); + const uint32_t secondYRangeMax = static_cast(std::max(0, std::min(static_cast(bufferHeight), secondUnderlineYOffset + static_cast(maxUnderlineHeight)))); + + // Rewind bitmapBuffer pointer, and skip secondYRangeMin line. + bitmapBuffer = reinterpret_cast(glyphData.bitmapBuffer.GetBuffer()) + yRangeMin * glyphData.width; + + for(uint32_t y = secondYRangeMin; y < secondYRangeMax; y++) + { + for(uint32_t x = xRangeMin; x < xRangeMax; x++) + { + // Note : this is same logic as bitmap[y][x] = underlineColor; + *(bitmapBuffer + x) = packedUnderlineColor; + } + bitmapBuffer += glyphData.width; + } + } + } +} + +/// Draws the specified strikethrough color to the buffer +void DrawStrikethrough(const uint32_t bufferWidth, + const uint32_t bufferHeight, + GlyphData& glyphData, + const float baseline, + const float strikethroughStartingYPosition, + const float maxStrikethroughHeight, + const float lineExtentLeft, + const float lineExtentRight, + const StrikethroughStyleProperties& commonStrikethroughProperties, + const StrikethroughStyleProperties& currentStrikethroughProperties, + const LineRun& line) +{ + const Vector4& strikethroughColor = currentStrikethroughProperties.colorDefined ? currentStrikethroughProperties.color : commonStrikethroughProperties.color; + + const uint32_t yRangeMin = static_cast(strikethroughStartingYPosition); + const uint32_t yRangeMax = std::min(bufferHeight, static_cast(strikethroughStartingYPosition + maxStrikethroughHeight)); + const uint32_t xRangeMin = static_cast(glyphData.horizontalOffset + lineExtentLeft); + const uint32_t xRangeMax = std::min(bufferWidth, static_cast(glyphData.horizontalOffset + lineExtentRight + 1)); // Due to include last point, we add 1 here + + // If current glyph don't need to be rendered, just ignore. + if(yRangeMax <= yRangeMin || xRangeMax <= xRangeMin) + { + return; + } + + // We can optimize by memset when strikethroughColor.a is near zero + uint8_t strikethroughColorAlpha = static_cast(strikethroughColor.a * 255.f); + + uint32_t* bitmapBuffer = reinterpret_cast(glyphData.bitmapBuffer.GetBuffer()); + + // Skip yRangeMin line. + bitmapBuffer += yRangeMin * glyphData.width; + + if(strikethroughColorAlpha == 0) + { + for(uint32_t y = yRangeMin; y < yRangeMax; y++) + { + // We can use memset. + memset(bitmapBuffer + xRangeMin, 0, (xRangeMax - xRangeMin) * sizeof(uint32_t)); + bitmapBuffer += glyphData.width; + } + } + else + { + uint32_t packedStrikethroughColor = 0u; + uint8_t* packedStrikethroughColorBuffer = reinterpret_cast(&packedStrikethroughColor); + + // Write the color to the pixel buffer + *(packedStrikethroughColorBuffer + 3u) = strikethroughColorAlpha; + *(packedStrikethroughColorBuffer + 2u) = static_cast(strikethroughColor.b * strikethroughColorAlpha); + *(packedStrikethroughColorBuffer + 1u) = static_cast(strikethroughColor.g * strikethroughColorAlpha); + *(packedStrikethroughColorBuffer) = static_cast(strikethroughColor.r * strikethroughColorAlpha); + + for(uint32_t y = yRangeMin; y < yRangeMax; y++) + { + for(uint32_t x = xRangeMin; x < xRangeMax; x++) + { + // Note : this is same logic as bitmap[y][x] = strikethroughColor; + *(bitmapBuffer + x) = packedStrikethroughColor; + } + bitmapBuffer += glyphData.width; + } + } +} + +/// Helper functions to create image buffer + +struct InputParameterForEachLine +{ + const uint32_t bufferWidth; + const uint32_t bufferHeight; + const int32_t horizontalOffset; + + const Vector2& styleOffset; ///< If style is STYLE_OUTLINE, outline offset. If style is STYLE_SHADOW, shadow offset. Otherwise, zero. + + const GlyphIndex fromGlyphIndex; + const GlyphIndex toGlyphIndex; + + // Elide text info + const GlyphIndex startIndexOfGlyphs; + const GlyphIndex endIndexOfGlyphs; + const GlyphIndex firstMiddleIndexOfElidedGlyphs; + const GlyphIndex secondMiddleIndexOfElidedGlyphs; + + const DevelText::VerticalLineAlignment::Type verticalLineAlignType; + const DevelText::EllipsisPosition::Type ellipsisPosition; + + const GlyphInfo* __restrict__ hyphens; + const Length* __restrict__ hyphenIndices; + const Length hyphensCount; + + const bool ignoreHorizontalAlignment : 1; +}; + +struct InputParameterForEachGlyph +{ + const Typesetter::Style style; + const Pixel::Format pixelFormat; + + const float outlineWidth; + + const float modelCharacterSpacing; + + const Vector4& defaultColor; ///< The default color for the text. + /// Or some color which depends on style value. (e.g. ShadowColor if style is STYLE_SHADOW) + + const Vector& underlineRuns; + const Vector& strikethroughRuns; + const Vector& characterSpacingGlyphRuns; + + const GlyphInfo* const __restrict__ glyphsBuffer; + const Character* __restrict__ textBuffer; + const CharacterIndex* __restrict__ glyphToCharacterMapBuffer; + + const Vector2* const __restrict__ positionBuffer; + + const Vector4* const __restrict__ colorsBuffer; + const TextAbstraction::ColorIndex* const __restrict__ colorIndexBuffer; + + const UnderlineStyleProperties modelUnderlineProperties; + const StrikethroughStyleProperties modelStrikethroughProperties; + + const bool underlineEnabled : 1; + const bool strikethroughEnabled : 1; + const bool cutoutEnabled : 1; + + const bool removeFrontInset : 1; + const bool removeBackInset : 1; + + const bool useDefaultColor : 1; +}; + +struct OutputParameterForEachGlyph +{ + UnderlineStyleProperties& currentUnderlineProperties; + + float& maxUnderlineHeight; + bool& thereAreUnderlinedGlyphs; + + StrikethroughStyleProperties& currentStrikethroughProperties; + + float& maxStrikethroughHeight; + bool& thereAreStrikethroughGlyphs; + + float& currentUnderlinePosition; + + float& baseline; + float& lineExtentLeft; + float& lineExtentRight; + + FontId& lastFontId; +}; + +void CreateImageBufferForEachGlyph(TextAbstraction::FontClient fontClient, GlyphData& glyphData, GlyphIndex& glyphIndex, const GlyphIndex elidedGlyphIndex, const GlyphInfo* glyphInfo, const bool addHyphen, const InputParameterForEachGlyph& inputParamsForGlyph, OutputParameterForEachGlyph& outputParamsForGlyph) +{ + Vector::ConstIterator currentUnderlinedGlyphRunIt = inputParamsForGlyph.underlineRuns.End(); + const bool underlineGlyph = inputParamsForGlyph.underlineEnabled || IsGlyphUnderlined(glyphIndex, inputParamsForGlyph.underlineRuns, currentUnderlinedGlyphRunIt); + outputParamsForGlyph.currentUnderlineProperties = GetCurrentUnderlineProperties(glyphIndex, underlineGlyph, inputParamsForGlyph.underlineRuns, currentUnderlinedGlyphRunIt, inputParamsForGlyph.modelUnderlineProperties); + float currentUnderlineHeight = outputParamsForGlyph.currentUnderlineProperties.height; + + outputParamsForGlyph.thereAreUnderlinedGlyphs = outputParamsForGlyph.thereAreUnderlinedGlyphs || underlineGlyph; + + Vector::ConstIterator currentStrikethroughGlyphRunIt = inputParamsForGlyph.strikethroughRuns.End(); + const bool strikethroughGlyph = inputParamsForGlyph.strikethroughEnabled || IsGlyphStrikethrough(glyphIndex, inputParamsForGlyph.strikethroughRuns, currentStrikethroughGlyphRunIt); + outputParamsForGlyph.currentStrikethroughProperties = GetCurrentStrikethroughProperties(glyphIndex, strikethroughGlyph, inputParamsForGlyph.strikethroughRuns, currentStrikethroughGlyphRunIt, inputParamsForGlyph.modelStrikethroughProperties); + float currentStrikethroughHeight = outputParamsForGlyph.currentStrikethroughProperties.height; + + outputParamsForGlyph.thereAreStrikethroughGlyphs = outputParamsForGlyph.thereAreStrikethroughGlyphs || strikethroughGlyph; + + // Are we still using the same fontId as previous + if((glyphInfo->fontId != outputParamsForGlyph.lastFontId) && (strikethroughGlyph || underlineGlyph)) + { + // We need to fetch fresh font underline metrics + FontMetrics fontMetrics; + fontClient.GetFontMetrics(glyphInfo->fontId, fontMetrics); + + //The currentUnderlinePosition will be used for both Underline and/or Strikethrough + outputParamsForGlyph.currentUnderlinePosition = FetchUnderlinePositionFromFontMetrics(fontMetrics); + + if(underlineGlyph) + { + CalcualteUnderlineHeight(fontMetrics, currentUnderlineHeight, outputParamsForGlyph.maxUnderlineHeight); + } + + if(strikethroughGlyph) + { + CalcualteStrikethroughHeight(currentStrikethroughHeight, outputParamsForGlyph.maxStrikethroughHeight); + } + + // Update lastFontId because fontId is changed + outputParamsForGlyph.lastFontId = glyphInfo->fontId; // Prevents searching for existing blocksizes when string of the same fontId. + } + + // Retrieves the glyph's position. + Vector2 position = *(inputParamsForGlyph.positionBuffer + elidedGlyphIndex); + + if(addHyphen) + { + GlyphInfo tempInfo = *(inputParamsForGlyph.glyphsBuffer + elidedGlyphIndex); + const float characterSpacing = GetGlyphCharacterSpacing(glyphIndex, inputParamsForGlyph.characterSpacingGlyphRuns, inputParamsForGlyph.modelCharacterSpacing); + const float calculatedAdvance = GetCalculatedAdvance(*(inputParamsForGlyph.textBuffer + (*(inputParamsForGlyph.glyphToCharacterMapBuffer + elidedGlyphIndex))), characterSpacing, tempInfo.advance); + position.x = position.x + calculatedAdvance - tempInfo.xBearing + glyphInfo->xBearing; + position.y = -glyphInfo->yBearing; + } + + if(outputParamsForGlyph.baseline < position.y + glyphInfo->yBearing) + { + outputParamsForGlyph.baseline = position.y + glyphInfo->yBearing; + } + + // Calculate the positions of leftmost and rightmost glyphs in the current line + if(inputParamsForGlyph.removeFrontInset) + { + if(position.x < outputParamsForGlyph.lineExtentLeft) + { + outputParamsForGlyph.lineExtentLeft = position.x; + } + } + else + { + const float originPositionLeft = position.x - glyphInfo->xBearing; + if(originPositionLeft < outputParamsForGlyph.lineExtentLeft) + { + outputParamsForGlyph.lineExtentLeft = originPositionLeft; + } + } + + if(inputParamsForGlyph.removeBackInset) + { + if(position.x + glyphInfo->width > outputParamsForGlyph.lineExtentRight) + { + outputParamsForGlyph.lineExtentRight = position.x + glyphInfo->width; + } + } + else + { + const float originPositionRight = position.x - glyphInfo->xBearing + glyphInfo->advance; + if(originPositionRight > outputParamsForGlyph.lineExtentRight) + { + outputParamsForGlyph.lineExtentRight = originPositionRight; + } + } + + // Retrieves the glyph's color. + const ColorIndex colorIndex = inputParamsForGlyph.useDefaultColor ? 0u : *(inputParamsForGlyph.colorIndexBuffer + glyphIndex); + + Vector4 color; + if(inputParamsForGlyph.style == Typesetter::STYLE_SHADOW) + { + color = inputParamsForGlyph.defaultColor; + } + else if(inputParamsForGlyph.style == Typesetter::STYLE_OUTLINE) + { + color = inputParamsForGlyph.defaultColor; + } + else + { + color = (inputParamsForGlyph.useDefaultColor || (0u == colorIndex)) ? inputParamsForGlyph.defaultColor : *(inputParamsForGlyph.colorsBuffer + (colorIndex - 1u)); + } + + if(inputParamsForGlyph.style == Typesetter::STYLE_NONE && inputParamsForGlyph.cutoutEnabled) + { + // Temporarily adjust the transparency to 1.f + color.a = 1.f; + } + + // Premultiply alpha + color.r *= color.a; + color.g *= color.a; + color.b *= color.a; + + // Retrieves the glyph's bitmap. + glyphData.glyphBitmap.buffer = nullptr; + glyphData.glyphBitmap.width = glyphInfo->width; // Desired width and height. + glyphData.glyphBitmap.height = glyphInfo->height; + + float outlineWidth = inputParamsForGlyph.outlineWidth; + + if(inputParamsForGlyph.style != Typesetter::STYLE_OUTLINE && inputParamsForGlyph.style != Typesetter::STYLE_SHADOW) + { + // Don't render outline for other styles + outlineWidth = 0.0f; + } + + if(inputParamsForGlyph.style != Typesetter::STYLE_UNDERLINE && inputParamsForGlyph.style != Typesetter::STYLE_STRIKETHROUGH) + { + fontClient.CreateBitmap(glyphInfo->fontId, + glyphInfo->index, + glyphInfo->isItalicRequired, + glyphInfo->isBoldRequired, + glyphData.glyphBitmap, + static_cast(outlineWidth)); + } + + // Sets the glyph's bitmap into the bitmap of the whole text. + if(nullptr != glyphData.glyphBitmap.buffer) + { + if(inputParamsForGlyph.style == Typesetter::STYLE_OUTLINE) + { + // Set the position offset for the current glyph + glyphData.horizontalOffset -= glyphData.glyphBitmap.outlineOffsetX; + glyphData.verticalOffset -= glyphData.glyphBitmap.outlineOffsetY; + } + + // Set the buffer of the glyph's bitmap into the final bitmap's buffer + TypesetGlyph(glyphData, + &position, + &color, + inputParamsForGlyph.style, + inputParamsForGlyph.pixelFormat); + + if(inputParamsForGlyph.style == Typesetter::STYLE_OUTLINE) + { + // Reset the position offset for the next glyph + glyphData.horizontalOffset += glyphData.glyphBitmap.outlineOffsetX; + glyphData.verticalOffset += glyphData.glyphBitmap.outlineOffsetY; + } + + // free the glyphBitmap.buffer if it is owner of buffer + if(glyphData.glyphBitmap.isBufferOwned) + { + free(glyphData.glyphBitmap.buffer); + glyphData.glyphBitmap.isBufferOwned = false; + } + glyphData.glyphBitmap.buffer = nullptr; + } +} + +void CreateImageBufferForEachLine(TextAbstraction::FontClient fontClient, GlyphData& glyphData, Length& hyphenIndex, const LineRun& line, const bool isFirstLine, const InputParameterForEachLine& inputParamsForLine, const InputParameterForEachGlyph& inputParamsForGlyph) +{ + // Sets the horizontal offset of the line. + glyphData.horizontalOffset = inputParamsForLine.ignoreHorizontalAlignment ? 0 : static_cast(line.alignmentOffset); + glyphData.horizontalOffset += inputParamsForLine.horizontalOffset; + + // Increases the vertical offset with the line's ascender. + glyphData.verticalOffset += static_cast(line.ascender + GetPreOffsetVerticalLineAlignment(line, inputParamsForLine.verticalLineAlignType)); + + if(inputParamsForGlyph.style == Typesetter::STYLE_OUTLINE) + { + glyphData.horizontalOffset -= inputParamsForGlyph.outlineWidth; + glyphData.horizontalOffset += inputParamsForLine.styleOffset.x; + if(isFirstLine) + { + // Only need to add the vertical outline offset for the first line + glyphData.verticalOffset -= inputParamsForGlyph.outlineWidth; + glyphData.verticalOffset += inputParamsForLine.styleOffset.y; + } + } + else if(inputParamsForGlyph.style == Typesetter::STYLE_SHADOW) + { + glyphData.horizontalOffset += inputParamsForLine.styleOffset.x - inputParamsForGlyph.outlineWidth; // if outline enabled then shadow should offset from outline + + if(isFirstLine) + { + // Only need to add the vertical shadow offset for first line + glyphData.verticalOffset += inputParamsForLine.styleOffset.y - inputParamsForGlyph.outlineWidth; + } + } + + bool thereAreUnderlinedGlyphs = false; + bool thereAreStrikethroughGlyphs = false; + + float currentUnderlinePosition = 0.0f; + auto currentUnderlineProperties = inputParamsForGlyph.modelUnderlineProperties; + float maxUnderlineHeight = currentUnderlineProperties.height; + + auto currentStrikethroughProperties = inputParamsForGlyph.modelStrikethroughProperties; + float maxStrikethroughHeight = currentStrikethroughProperties.height; + + FontId lastFontId = 0; + + float lineExtentLeft = inputParamsForLine.bufferWidth; + float lineExtentRight = 0.0f; + float baseline = 0.0f; + bool addHyphen = false; + + // Traverses the glyphs of the line. + const GlyphIndex startGlyphIndex = std::max(std::max(line.glyphRun.glyphIndex, inputParamsForLine.startIndexOfGlyphs), inputParamsForLine.fromGlyphIndex); + GlyphIndex endGlyphIndex = (line.isSplitToTwoHalves ? line.glyphRunSecondHalf.glyphIndex + line.glyphRunSecondHalf.numberOfGlyphs : line.glyphRun.glyphIndex + line.glyphRun.numberOfGlyphs) - 1u; + endGlyphIndex = std::min(std::min(endGlyphIndex, inputParamsForLine.endIndexOfGlyphs), inputParamsForLine.toGlyphIndex); + + for(GlyphIndex glyphIndex = startGlyphIndex; glyphIndex <= endGlyphIndex; ++glyphIndex) + { + //To handle START case of ellipsis, the first glyph has been shifted + //glyphIndex represent indices in whole glyphs but elidedGlyphIndex represents indices in elided Glyphs + GlyphIndex elidedGlyphIndex = glyphIndex - inputParamsForLine.startIndexOfGlyphs; + + //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. + if(inputParamsForLine.ellipsisPosition == DevelText::EllipsisPosition::MIDDLE) + { + if(glyphIndex > inputParamsForLine.firstMiddleIndexOfElidedGlyphs && + glyphIndex < inputParamsForLine.secondMiddleIndexOfElidedGlyphs) + { + // Ignore any glyph that removed for MIDDLE ellipsis + continue; + } + if(glyphIndex >= inputParamsForLine.secondMiddleIndexOfElidedGlyphs) + { + elidedGlyphIndex -= (inputParamsForLine.secondMiddleIndexOfElidedGlyphs - inputParamsForLine.firstMiddleIndexOfElidedGlyphs - 1u); + } + } + + // Retrieve the glyph's info. + const GlyphInfo* glyphInfo; + + if(addHyphen && inputParamsForLine.hyphens) + { + glyphInfo = inputParamsForLine.hyphens + hyphenIndex; + hyphenIndex++; + } + else + { + glyphInfo = inputParamsForGlyph.glyphsBuffer + elidedGlyphIndex; + } + + if((glyphInfo->width < Math::MACHINE_EPSILON_1000) || + (glyphInfo->height < Math::MACHINE_EPSILON_1000)) + { + // Nothing to do if the glyph's width or height is zero. + continue; + } + + // Collect output l-values + // clang-format off + OutputParameterForEachGlyph outputParamsForGlyph{currentUnderlineProperties, + + maxUnderlineHeight, + thereAreUnderlinedGlyphs, + + currentStrikethroughProperties, + + maxStrikethroughHeight, + thereAreStrikethroughGlyphs, + + currentUnderlinePosition, + + baseline, + lineExtentLeft, + lineExtentRight, + + lastFontId}; + // clang-format on + + CreateImageBufferForEachGlyph(fontClient, glyphData, glyphIndex, elidedGlyphIndex, glyphInfo, addHyphen, inputParamsForGlyph, outputParamsForGlyph); + + if(inputParamsForLine.hyphenIndices) + { + while((hyphenIndex < inputParamsForLine.hyphensCount) && (glyphIndex > inputParamsForLine.hyphenIndices[hyphenIndex])) + { + hyphenIndex++; + } + + addHyphen = ((hyphenIndex < inputParamsForLine.hyphensCount) && ((glyphIndex + 1) == inputParamsForLine.hyphenIndices[hyphenIndex])); + if(addHyphen) + { + glyphIndex--; + } + } + } + + // Draw the underline from the leftmost glyph to the rightmost glyph + if(thereAreUnderlinedGlyphs && inputParamsForGlyph.style == Typesetter::STYLE_UNDERLINE) + { + DrawUnderline(inputParamsForLine.bufferWidth, inputParamsForLine.bufferHeight, glyphData, baseline, currentUnderlinePosition, maxUnderlineHeight, lineExtentLeft, lineExtentRight, inputParamsForGlyph.modelUnderlineProperties, currentUnderlineProperties, line); + } + + // Draw the background color from the leftmost glyph to the rightmost glyph + if(inputParamsForGlyph.style == Typesetter::STYLE_BACKGROUND) + { + DrawBackgroundColor(inputParamsForGlyph.defaultColor, inputParamsForLine.bufferWidth, inputParamsForLine.bufferHeight, glyphData, baseline, line, lineExtentLeft, lineExtentRight); + } + + // Draw the strikethrough from the leftmost glyph to the rightmost glyph + if(thereAreStrikethroughGlyphs && inputParamsForGlyph.style == Typesetter::STYLE_STRIKETHROUGH) + { + //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. + const float 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. + DrawStrikethrough(inputParamsForLine.bufferWidth, inputParamsForLine.bufferHeight, glyphData, baseline, strikethroughStartingYPosition, maxStrikethroughHeight, lineExtentLeft, lineExtentRight, inputParamsForGlyph.modelStrikethroughProperties, currentStrikethroughProperties, line); + } + + // Increases the vertical offset with the line's descender & line spacing. + glyphData.verticalOffset += static_cast(-line.descender + GetPostOffsetVerticalLineAlignment(line, inputParamsForLine.verticalLineAlignType)); +} + +/// Helper functions to create image buffer end + +/** + * @brief Create an initialized image buffer filled with transparent color. + * + * Creates the pixel data used to generate the final image with the given size. + * + * @param[in] bufferWidth The width of the image buffer. + * @param[in] bufferHeight The height of the image buffer. + * @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). + * + * @return An image buffer. + */ +inline Devel::PixelBuffer CreateTransparentImageBuffer(const uint32_t bufferWidth, const uint32_t bufferHeight, const Pixel::Format pixelFormat) +{ + Devel::PixelBuffer imageBuffer = Devel::PixelBuffer::New(bufferWidth, bufferHeight, pixelFormat); + + if(Pixel::RGBA8888 == pixelFormat) + { + const uint32_t bufferSizeInt = bufferWidth * bufferHeight; + const size_t bufferSizeChar = sizeof(uint32_t) * static_cast(bufferSizeInt); + memset(imageBuffer.GetBuffer(), 0, bufferSizeChar); + } + else + { + memset(imageBuffer.GetBuffer(), 0, static_cast(bufferWidth * bufferHeight)); + } + + return imageBuffer; +} + +} // namespace + +ViewModel* Typesetter::Impl::GetViewModel() +{ + return mModel.get(); +} + +void Typesetter::Impl::SetFontClient(TextAbstraction::FontClient& fontClient) +{ + mFontClient = fontClient; +} + +TextAbstraction::FontClient& Typesetter::Impl::GetFontClient() +{ + return mFontClient; +} + +Devel::PixelBuffer Typesetter::Impl::CreateTransparentImageBuffer(const uint32_t bufferWidth, const uint32_t bufferHeight, const Pixel::Format pixelFormat) +{ + return Dali::Toolkit::Text::CreateTransparentImageBuffer(bufferWidth, bufferHeight, pixelFormat); +} + +void Typesetter::Impl::DrawGlyphsBackground(Devel::PixelBuffer& buffer, const uint32_t bufferWidth, const uint32_t bufferHeight, const bool ignoreHorizontalAlignment, const int32_t horizontalOffset, const int32_t verticalOffset) +{ + // Use l-value to make ensure it is not nullptr, so compiler happy. + auto& viewModel = *(mModel.get()); + + // Retrieve lines, glyphs, positions and colors from the view model. + const Length modelNumberOfLines = viewModel.GetNumberOfLines(); + const LineRun* const modelLinesBuffer = viewModel.GetLines(); + const Length numberOfGlyphs = viewModel.GetNumberOfGlyphs(); + const GlyphInfo* const glyphsBuffer = viewModel.GetGlyphs(); + const Vector2* const positionBuffer = viewModel.GetLayout(); + const Vector4* const backgroundColorsBuffer = viewModel.GetBackgroundColors(); + const ColorIndex* const backgroundColorIndicesBuffer = viewModel.GetBackgroundColorIndices(); + const bool removeFrontInset = viewModel.IsRemoveFrontInset(); + const bool removeBackInset = viewModel.IsRemoveBackInset(); + + const DevelText::VerticalLineAlignment::Type verticalLineAlignType = viewModel.GetVerticalLineAlignment(); + + // Create and initialize the pixel buffer. + GlyphData glyphData; + glyphData.verticalOffset = verticalOffset; + glyphData.width = bufferWidth; + glyphData.height = bufferHeight; + glyphData.bitmapBuffer = buffer; + glyphData.horizontalOffset = 0; + + ColorIndex prevBackgroundColorIndex = 0; + ColorIndex backgroundColorIndex = 0; + + // Traverses the lines of the text. + for(LineIndex lineIndex = 0u; lineIndex < modelNumberOfLines; ++lineIndex) + { + const LineRun& line = *(modelLinesBuffer + lineIndex); + + // Sets the horizontal offset of the line. + glyphData.horizontalOffset = ignoreHorizontalAlignment ? 0 : static_cast(line.alignmentOffset); + glyphData.horizontalOffset += horizontalOffset; + + // Increases the vertical offset with the line's ascender. + glyphData.verticalOffset += static_cast(line.ascender + GetPreOffsetVerticalLineAlignment(line, verticalLineAlignType)); + + float left = bufferWidth; + float right = 0.0f; + float baseline = 0.0f; + + // Traverses the glyphs of the line. + const GlyphIndex endGlyphIndex = std::min(numberOfGlyphs, line.glyphRun.glyphIndex + line.glyphRun.numberOfGlyphs); + for(GlyphIndex glyphIndex = line.glyphRun.glyphIndex; glyphIndex < endGlyphIndex; ++glyphIndex) + { + // Retrieve the glyph's info. + const GlyphInfo* const glyphInfo = glyphsBuffer + glyphIndex; + + if((glyphInfo->width < Math::MACHINE_EPSILON_1000) || + (glyphInfo->height < Math::MACHINE_EPSILON_1000)) + { + // Nothing to do if default background color, the glyph's width or height is zero. + continue; + } + + backgroundColorIndex = (nullptr == backgroundColorsBuffer) ? 0u : *(backgroundColorIndicesBuffer + glyphIndex); + + if((backgroundColorIndex != prevBackgroundColorIndex) && + (prevBackgroundColorIndex != 0u)) + { + const Vector4& backgroundColor = *(backgroundColorsBuffer + prevBackgroundColorIndex - 1u); + DrawBackgroundColor(backgroundColor, bufferWidth, bufferHeight, glyphData, baseline, line, left, right); + } + + if(backgroundColorIndex == 0u) + { + prevBackgroundColorIndex = backgroundColorIndex; + //if background color is the default do nothing + continue; + } + + // Retrieves the glyph's position. + const Vector2* const position = positionBuffer + glyphIndex; + + if(baseline < position->y + glyphInfo->yBearing) + { + baseline = position->y + glyphInfo->yBearing; + } + + // Calculate the positions of leftmost and rightmost glyphs in the current line + if(removeFrontInset) + { + if((position->x < left) || (backgroundColorIndex != prevBackgroundColorIndex)) + { + left = position->x; + } + } + else + { + const float originPositionLeft = position->x - glyphInfo->xBearing; + if((originPositionLeft < left) || (backgroundColorIndex != prevBackgroundColorIndex)) + { + left = originPositionLeft; + } + } + + if(removeBackInset) + { + if(position->x + glyphInfo->width > right) + { + right = position->x + glyphInfo->width; + } + } + else + { + const float originPositionRight = position->x - glyphInfo->xBearing + glyphInfo->advance; + if(originPositionRight > right) + { + right = originPositionRight; + } + } + + prevBackgroundColorIndex = backgroundColorIndex; + } + + //draw last background at line end if not default + if(backgroundColorIndex != 0u) + { + const Vector4& backgroundColor = *(backgroundColorsBuffer + backgroundColorIndex - 1u); + DrawBackgroundColor(backgroundColor, bufferWidth, bufferHeight, glyphData, baseline, line, left, right); + } + + // Increases the vertical offset with the line's descender. + glyphData.verticalOffset += static_cast(-line.descender + GetPostOffsetVerticalLineAlignment(line, verticalLineAlignType)); + } +} + +Devel::PixelBuffer Typesetter::Impl::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) +{ + // Use l-value to make ensure it is not nullptr, so compiler happy. + auto& viewModel = *(mModel.get()); + + // Retrieve lines, glyphs, positions and colors from the view model. + const Length modelNumberOfLines = viewModel.GetNumberOfLines(); + const LineRun* const __restrict__ modelLinesBuffer = viewModel.GetLines(); + const GlyphInfo* const __restrict__ glyphsBuffer = viewModel.GetGlyphs(); + const Vector2* const __restrict__ positionBuffer = viewModel.GetLayout(); + const Vector4* const __restrict__ colorsBuffer = viewModel.GetColors(); + const ColorIndex* const __restrict__ colorIndexBuffer = viewModel.GetColorIndices(); + const GlyphInfo* __restrict__ hyphens = viewModel.GetHyphens(); + const Length* __restrict__ hyphenIndices = viewModel.GetHyphenIndices(); + const Length hyphensCount = viewModel.GetHyphensCount(); + + // Create and initialize the pixel buffer. + GlyphData glyphData; + glyphData.verticalOffset = verticalOffset; + glyphData.width = bufferWidth; + glyphData.height = bufferHeight; + glyphData.bitmapBuffer = CreateTransparentImageBuffer(bufferWidth, bufferHeight, pixelFormat); + glyphData.horizontalOffset = 0; + + Length hyphenIndex = 0; + + const Character* __restrict__ textBuffer = viewModel.GetTextBuffer(); + const Vector& __restrict__ glyphToCharacterMap = viewModel.GetGlyphsToCharacters(); + const CharacterIndex* __restrict__ glyphToCharacterMapBuffer = glyphToCharacterMap.Begin(); + + // Get the underline runs. + const Length numberOfUnderlineRuns = viewModel.GetNumberOfUnderlineRuns(); + Vector underlineRuns; + underlineRuns.Resize(numberOfUnderlineRuns); + viewModel.GetUnderlineRuns(underlineRuns.Begin(), 0u, numberOfUnderlineRuns); + + // Get the strikethrough runs. + const Length numberOfStrikethroughRuns = viewModel.GetNumberOfStrikethroughRuns(); + Vector strikethroughRuns; + strikethroughRuns.Resize(numberOfStrikethroughRuns); + viewModel.GetStrikethroughRuns(strikethroughRuns.Begin(), 0u, numberOfStrikethroughRuns); + + // Get the character-spacing runs. + const Vector& __restrict__ characterSpacingGlyphRuns = viewModel.GetCharacterSpacingGlyphRuns(); + + // clang-format off + // Aggregate input parameter for each line from mModel + const InputParameterForEachLine inputParamsForLine{bufferWidth, + bufferHeight, + horizontalOffset, + + (style == Typesetter::STYLE_OUTLINE) ? viewModel.GetOutlineOffset() : + (style == Typesetter::STYLE_SHADOW) ? viewModel.GetShadowOffset() : + Vector2::ZERO, + + fromGlyphIndex, + toGlyphIndex, + + // Elided text info. Indices according to elided text and Ellipsis position. + viewModel.GetStartIndexOfElidedGlyphs(), + viewModel.GetEndIndexOfElidedGlyphs(), + viewModel.GetFirstMiddleIndexOfElidedGlyphs(), + viewModel.GetSecondMiddleIndexOfElidedGlyphs(), + + viewModel.GetVerticalLineAlignment(), + viewModel.GetEllipsisPosition(), + + hyphens, + hyphenIndices, + hyphensCount, + + ignoreHorizontalAlignment}; + + // Aggregate underline-style-properties from mModel + const UnderlineStyleProperties modelUnderlineProperties{viewModel.GetUnderlineType(), + viewModel.GetUnderlineColor(), + viewModel.GetUnderlineHeight(), + viewModel.GetDashedUnderlineGap(), + viewModel.GetDashedUnderlineWidth(), + true, + true, + true, + true, + true}; + + // Aggregate strikethrough-style-properties from mModel + const StrikethroughStyleProperties modelStrikethroughProperties{viewModel.GetStrikethroughColor(), + viewModel.GetStrikethroughHeight(), + true, + true}; + + + // Aggregate input parameter for each glyph from mModel + const InputParameterForEachGlyph inputParamsForGlyph{style, + pixelFormat, + + // Retrieves the glyph's outline width + static_cast(viewModel.GetOutlineWidth()), + + viewModel.GetCharacterSpacing(), + + (style == Typesetter::STYLE_OUTLINE) ? viewModel.GetOutlineColor() : + (style == Typesetter::STYLE_SHADOW) ? viewModel.GetShadowColor() : + (style == Typesetter::STYLE_BACKGROUND) ? viewModel.GetBackgroundColor() : + viewModel.GetDefaultColor(), + + underlineRuns, + strikethroughRuns, + characterSpacingGlyphRuns, + + glyphsBuffer, + textBuffer, + glyphToCharacterMapBuffer, + + positionBuffer, + + colorsBuffer, + colorIndexBuffer, + + modelUnderlineProperties, + modelStrikethroughProperties, + + viewModel.IsUnderlineEnabled(), + viewModel.IsStrikethroughEnabled(), + viewModel.IsCutoutEnabled(), + + viewModel.IsRemoveFrontInset(), + viewModel.IsRemoveBackInset(), + + // Whether to use the default color. + (nullptr == colorsBuffer)}; + // clang-format on + + // Traverses the lines of the text. + for(LineIndex lineIndex = 0u; lineIndex < modelNumberOfLines; ++lineIndex) + { + const LineRun& line = *(modelLinesBuffer + lineIndex); + CreateImageBufferForEachLine(mFontClient, glyphData, hyphenIndex, line, (lineIndex == 0u), inputParamsForLine, inputParamsForGlyph); + } + + return glyphData.bitmapBuffer; +} + +Typesetter::Impl::Impl(const ModelInterface* const model) +: mModel(std::make_unique(model)) +{ + // Default font client set. + mFontClient = TextAbstraction::FontClient::Get(); +} + +Typesetter::Impl::~Impl() = default; + +} // namespace Text + +} // namespace Toolkit + +} // namespace Dali diff --git a/dali-toolkit/internal/text/rendering/text-typesetter-impl.h b/dali-toolkit/internal/text/rendering/text-typesetter-impl.h new file mode 100644 index 0000000000..d0d7a3e126 --- /dev/null +++ b/dali-toolkit/internal/text/rendering/text-typesetter-impl.h @@ -0,0 +1,131 @@ +#ifndef DALI_TOOLKIT_TEXT_TYPESETTER_IMPL_H +#define DALI_TOOLKIT_TEXT_TYPESETTER_IMPL_H + +/* + * Copyright (c) 2024 Samsung Electronics Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +// EXTERNAL INCLUDES +#include +#include +#include +#include +#include +#include +#include +#include ///< for std::unique_ptr + +// INTERNAL INCLUDES +#include + +namespace Dali +{ +namespace Toolkit +{ +namespace Text +{ +class ModelInterface; +class ViewModel; + +/** + * @brief This class is seperated logics for TypeSetter. + * It will reduce the complexicy of typesetter logic. + */ +struct Typesetter::Impl +{ +public: + /** + * @brief Create an initialized image buffer filled with transparent color. + * + * Creates the pixel data used to generate the final image with the given size. + * + * @param[in] bufferWidth The width of the image buffer. + * @param[in] bufferHeight The height of the image buffer. + * @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). + * + * @return An image buffer. + */ + static Devel::PixelBuffer CreateTransparentImageBuffer(const uint32_t bufferWidth, const uint32_t bufferHeight, const Pixel::Format pixelFormat); + +public: // Constructor & Destructor + /** + * @brief Creates a Typesetter impl instance. + */ + Impl(const ModelInterface* const model); + + ~Impl(); + +public: + /** + * @brief Retrieves the pointer to the view model. + * + * @return A pointer to the view model. + */ + ViewModel* GetViewModel(); + + /** + * @brief Set the font client. + * + * Set the font client used in the update/render process of the text model. + * + * @param[in] fontClient The font client used by the Typesetter. + */ + void SetFontClient(TextAbstraction::FontClient& fontClient); + + /** + * @brief Get the font client. + * + * @return The font client used by the Typesetter. + */ + TextAbstraction::FontClient& GetFontClient(); + +public: // Image buffer creation + void DrawGlyphsBackground(Devel::PixelBuffer& buffer, const uint32_t bufferWidth, const uint32_t bufferHeight, const bool ignoreHorizontalAlignment, const int32_t horizontalOffset, const int32_t verticalOffset); + + /** + * @brief Create & draw the image buffer for the given range of the glyphs in the given style. + * + * Does the following operations: + * - Retrieves the data buffers from the text model. + * - Creates the pixel data used to generate the final image with the given size. + * - Traverse the visible glyphs, retrieve their bitmaps and compose the final pixel data. + * + * @param[in] bufferWidth The width of the image buffer. + * @param[in] bufferHeight The height of the image buffer. + * @param[in] style The style of the text. + * @param[in] ignoreHorizontalAlignment Whether to ignore the horizontal alignment, not ignored by default. + * @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). + * @param[in] horizontalOffset The horizontal offset to be added to the glyph's position. + * @param[in] verticalOffset The vertical offset to be added to the glyph's position. + * @param[in] fromGlyphIndex The index of the first glyph within the text to be drawn + * @param[in] toGlyphIndex The index of the last glyph within the text to be drawn + * + * @return An image buffer with the text. + */ + Devel::PixelBuffer 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 TextAbstraction::GlyphIndex fromGlyphIndex, const TextAbstraction::GlyphIndex toGlyphIndex); + +private: + std::unique_ptr mModel; + TextAbstraction::FontClient mFontClient; +}; + +} // namespace Text + +} // namespace Toolkit + +} // namespace Dali + +#endif // DALI_TOOLKIT_TEXT_TYPESETTER_IMPL_H diff --git a/dali-toolkit/internal/text/rendering/text-typesetter.cpp b/dali-toolkit/internal/text/rendering/text-typesetter.cpp index 503b39438a..52747a3ccd 100644 --- a/dali-toolkit/internal/text/rendering/text-typesetter.cpp +++ b/dali-toolkit/internal/text/rendering/text-typesetter.cpp @@ -19,13 +19,13 @@ #include // EXTERNAL INCLUDES -#include #include #include #include #include #include #include +#include // INTERNAL INCLUDES #include @@ -34,6 +34,7 @@ #include #include #include +#include #include namespace Dali @@ -46,9 +47,6 @@ namespace { DALI_INIT_TRACE_FILTER(gTraceFilter, DALI_TRACE_TEXT_PERFORMANCE_MARKER, false); -const float HALF(0.5f); -const float ONE_AND_A_HALF(1.5f); - /** * @brief Fast multiply & divide by 255. It wiil be useful when we applying alpha value in color * @@ -76,746 +74,7 @@ inline uint8_t MultiplyAndSummationAndNormalizeColor(const uint8_t x1, const uin const uint32_t xy1 = static_cast(x1) * y1; const uint32_t xy2 = static_cast(x2) * y2; const uint32_t res = std::min(65025u, xy1 + xy2); // 65025 is 255 * 255. - return ((res + ((res + 257) >> 8)) >> 8); // fast divide by 255. -} - -/// Helper macro define for glyph typesetter. It will reduce some duplicated code line. -// clang-format off -/** - * @brief Prepare decode glyph bitmap data. It must be call END_GLYPH_BITMAP end of same scope. - */ -#define BEGIN_GLYPH_BITMAP(data) \ -{ \ - uint32_t glyphOffet = 0u; \ - const bool useLocalScanline = data.glyphBitmap.compressionType != TextAbstraction::GlyphBufferData::CompressionType::NO_COMPRESSION; \ - uint8_t* __restrict__ glyphScanline = useLocalScanline ? (uint8_t*)malloc(data.glyphBitmap.width * glyphPixelSize) : data.glyphBitmap.buffer; \ - DALI_ASSERT_ALWAYS(glyphScanline && "Glyph scanline for buffer is null!"); - -/** - * @brief Macro to skip useless line fast. - */ -#define SKIP_GLYPH_SCANLINE(skipLine) \ -if(useLocalScanline) \ -{ \ - for(int32_t lineIndex = 0; lineIndex < skipLine; ++lineIndex) \ - { \ - TextAbstraction::GlyphBufferData::DecompressScanline(data.glyphBitmap, glyphScanline, glyphOffet); \ - } \ -} \ -else \ -{ \ - glyphScanline += skipLine * static_cast(data.glyphBitmap.width * glyphPixelSize); \ -} - -/** - * @brief Prepare scanline of glyph bitmap data per each lines. It must be call END_GLYPH_SCANLINE_DECODE end of same scope. - */ -#define BEGIN_GLYPH_SCANLINE_DECODE(data) \ -{ \ - if(useLocalScanline) \ - { \ - TextAbstraction::GlyphBufferData::DecompressScanline(data.glyphBitmap, glyphScanline, glyphOffet); \ - } - -/** - * @brief Finalize scanline of glyph bitmap data per each lines. - */ -#define END_GLYPH_SCANLINE_DECODE(data) \ - if(!useLocalScanline) \ - { \ - glyphScanline += data.glyphBitmap.width * glyphPixelSize; \ - } \ -} // For ensure that we call BEGIN_GLYPH_SCANLINE_DECODE before - -/** - * @brief Finalize decode glyph bitmap data. - */ -#define END_GLYPH_BITMAP() \ - if(useLocalScanline) \ - { \ - free(glyphScanline); \ - } \ -} // For ensure that we call BEGIN_GLYPH_BITMAP before - -// clang-format on -/// Helper macro define end. - -/** - * @brief Data struct used to set the buffer of the glyph's bitmap into the final bitmap's buffer. - */ -struct GlyphData -{ - Devel::PixelBuffer bitmapBuffer; ///< The buffer of the whole bitmap. The format is RGBA8888. - Vector2* position; ///< The position of the glyph. - TextAbstraction::GlyphBufferData glyphBitmap; ///< The glyph's bitmap. - uint32_t width; ///< The bitmap's width. - uint32_t height; ///< The bitmap's height. - int32_t horizontalOffset; ///< The horizontal offset to be added to the 'x' glyph's position. - int32_t verticalOffset; ///< The vertical offset to be added to the 'y' glyph's position. -}; - -/** - * @brief Sets the glyph's buffer into the bitmap's buffer. - * - * @param[in, out] data Struct which contains the glyph's data and the bitmap's data. - * @param[in] position The position of the glyph. - * @param[in] color The color of the glyph. - * @param[in] style The style of the text. - * @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). - */ -void TypesetGlyph(GlyphData& __restrict__ data, - const Vector2* const __restrict__ position, - const Vector4* const __restrict__ color, - const Typesetter::Style style, - const Pixel::Format pixelFormat) -{ - if((0u == data.glyphBitmap.width) || (0u == data.glyphBitmap.height)) - { - // Nothing to do if the width or height of the buffer is zero. - return; - } - - // Initial vertical / horizontal offset. - const int32_t yOffset = data.verticalOffset + position->y; - const int32_t xOffset = data.horizontalOffset + position->x; - - // Whether the given glyph is a color one. - const bool isColorGlyph = data.glyphBitmap.isColorEmoji || data.glyphBitmap.isColorBitmap; - const uint32_t glyphPixelSize = Pixel::GetBytesPerPixel(data.glyphBitmap.format); - const uint32_t glyphAlphaIndex = (glyphPixelSize > 0u) ? glyphPixelSize - 1u : 0u; - - // Determinate iterator range. - const int32_t lineIndexRangeMin = std::max(0, -yOffset); - const int32_t lineIndexRangeMax = std::min(static_cast(data.glyphBitmap.height), static_cast(data.height) - yOffset); - const int32_t indexRangeMin = std::max(0, -xOffset); - const int32_t indexRangeMax = std::min(static_cast(data.glyphBitmap.width), static_cast(data.width) - xOffset); - - // If current glyph don't need to be rendered, just ignore. - if(lineIndexRangeMax <= lineIndexRangeMin || indexRangeMax <= indexRangeMin) - { - return; - } - - if(Pixel::RGBA8888 == pixelFormat) - { - uint32_t* __restrict__ bitmapBuffer = reinterpret_cast(data.bitmapBuffer.GetBuffer()); - // Skip basic line. - bitmapBuffer += (lineIndexRangeMin + yOffset) * static_cast(data.width); - - // Fast-cut if style is MASK or OUTLINE. Outline not shown for color glyph. - // Just overwrite transparent color and return. - if(isColorGlyph && (Typesetter::STYLE_MASK == style || Typesetter::STYLE_OUTLINE == style)) - { - for(int32_t lineIndex = lineIndexRangeMin; lineIndex < lineIndexRangeMax; ++lineIndex) - { - // We can use memset here. - memset(bitmapBuffer + xOffset + indexRangeMin, 0, (indexRangeMax - indexRangeMin) * sizeof(uint32_t)); - bitmapBuffer += data.width; - } - return; - } - - const bool swapChannelsBR = Pixel::BGRA8888 == data.glyphBitmap.format; - - // Precalculate input color's packed result. - uint32_t packedInputColor = 0u; - uint8_t* __restrict__ packedInputColorBuffer = reinterpret_cast(&packedInputColor); - - *(packedInputColorBuffer + 3u) = static_cast(color->a * 255); - *(packedInputColorBuffer + 2u) = static_cast(color->b * 255); - *(packedInputColorBuffer + 1u) = static_cast(color->g * 255); - *(packedInputColorBuffer) = static_cast(color->r * 255); - - // Prepare glyph bitmap - BEGIN_GLYPH_BITMAP(data); - - // Skip basic line of glyph. - SKIP_GLYPH_SCANLINE(lineIndexRangeMin); - - // Traverse the pixels of the glyph line per line. - if(isColorGlyph) - { - for(int32_t lineIndex = lineIndexRangeMin; lineIndex < lineIndexRangeMax; ++lineIndex) - { - BEGIN_GLYPH_SCANLINE_DECODE(data); - - for(int32_t index = indexRangeMin; index < indexRangeMax; ++index) - { - const int32_t xOffsetIndex = xOffset + index; - - // Retrieves the color from the color glyph. - uint32_t packedColorGlyph = *(reinterpret_cast(glyphScanline + (index << 2))); - uint8_t* __restrict__ packedColorGlyphBuffer = reinterpret_cast(&packedColorGlyph); - - // Update the alpha channel. - const uint8_t colorAlpha = MultiplyAndNormalizeColor(*(packedInputColorBuffer + 3u), *(packedColorGlyphBuffer + 3u)); - *(packedColorGlyphBuffer + 3u) = colorAlpha; - - if(Typesetter::STYLE_SHADOW == style) - { - // The shadow of color glyph needs to have the shadow color. - *(packedColorGlyphBuffer + 2u) = MultiplyAndNormalizeColor(*(packedInputColorBuffer + 2u), colorAlpha); - *(packedColorGlyphBuffer + 1u) = MultiplyAndNormalizeColor(*(packedInputColorBuffer + 1u), colorAlpha); - *packedColorGlyphBuffer = MultiplyAndNormalizeColor(*packedInputColorBuffer, colorAlpha); - } - else - { - if(swapChannelsBR) - { - std::swap(*packedColorGlyphBuffer, *(packedColorGlyphBuffer + 2u)); // Swap B and R. - } - - *(packedColorGlyphBuffer + 2u) = MultiplyAndNormalizeColor(*(packedColorGlyphBuffer + 2u), colorAlpha); - *(packedColorGlyphBuffer + 1u) = MultiplyAndNormalizeColor(*(packedColorGlyphBuffer + 1u), colorAlpha); - *packedColorGlyphBuffer = MultiplyAndNormalizeColor(*packedColorGlyphBuffer, colorAlpha); - - if(data.glyphBitmap.isColorBitmap) - { - *(packedColorGlyphBuffer + 2u) = MultiplyAndNormalizeColor(*(packedInputColorBuffer + 2u), *(packedColorGlyphBuffer + 2u)); - *(packedColorGlyphBuffer + 1u) = MultiplyAndNormalizeColor(*(packedInputColorBuffer + 1u), *(packedColorGlyphBuffer + 1u)); - *packedColorGlyphBuffer = MultiplyAndNormalizeColor(*packedInputColorBuffer, *packedColorGlyphBuffer); - } - } - - // Set the color into the final pixel buffer. - *(bitmapBuffer + xOffsetIndex) = packedColorGlyph; - } - - bitmapBuffer += data.width; - - END_GLYPH_SCANLINE_DECODE(data); - } - } - else - { - for(int32_t lineIndex = lineIndexRangeMin; lineIndex < lineIndexRangeMax; ++lineIndex) - { - BEGIN_GLYPH_SCANLINE_DECODE(data); - - for(int32_t index = indexRangeMin; index < indexRangeMax; ++index) - { - // Update the alpha channel. - const uint8_t alpha = *(glyphScanline + index * glyphPixelSize + glyphAlphaIndex); - - // Copy non-transparent pixels only - if(alpha > 0u) - { - const int32_t xOffsetIndex = xOffset + index; - - // Check alpha of overlapped pixels - uint32_t& currentColor = *(bitmapBuffer + xOffsetIndex); - uint8_t* packedCurrentColorBuffer = reinterpret_cast(¤tColor); - - // For any pixel overlapped with the pixel in previous glyphs, make sure we don't - // overwrite a previous bigger alpha with a smaller alpha (in order to avoid - // semi-transparent gaps between joint glyphs with overlapped pixels, which could - // happen, for example, in the RTL text when we copy glyphs from right to left). - uint8_t currentAlpha = *(packedCurrentColorBuffer + 3u); - currentAlpha = std::max(currentAlpha, alpha); - if(currentAlpha == 255) - { - // Fast-cut to avoid float type operation. - currentColor = packedInputColor; - } - else - { - // Pack the given color into a 32bit buffer. The alpha channel will be updated later for each pixel. - // The format is RGBA8888. - uint32_t packedColor = 0u; - uint8_t* __restrict__ packedColorBuffer = reinterpret_cast(&packedColor); - - // Color is pre-muliplied with its alpha. - *(packedColorBuffer + 3u) = MultiplyAndNormalizeColor(*(packedInputColorBuffer + 3u), currentAlpha); - *(packedColorBuffer + 2u) = MultiplyAndNormalizeColor(*(packedInputColorBuffer + 2u), currentAlpha); - *(packedColorBuffer + 1u) = MultiplyAndNormalizeColor(*(packedInputColorBuffer + 1u), currentAlpha); - *(packedColorBuffer) = MultiplyAndNormalizeColor(*packedInputColorBuffer, currentAlpha); - - // Set the color into the final pixel buffer. - currentColor = packedColor; - } - } - } - - bitmapBuffer += data.width; - - END_GLYPH_SCANLINE_DECODE(data); - } - } - - END_GLYPH_BITMAP(); - } - else // Pixel::L8 - { - // Below codes required only if not color glyph. - if(!isColorGlyph) - { - uint8_t* __restrict__ bitmapBuffer = data.bitmapBuffer.GetBuffer(); - // Skip basic line. - bitmapBuffer += (lineIndexRangeMin + yOffset) * static_cast(data.width); - - // Prepare glyph bitmap - BEGIN_GLYPH_BITMAP(data); - - // Skip basic line of glyph. - SKIP_GLYPH_SCANLINE(lineIndexRangeMin); - - // Traverse the pixels of the glyph line per line. - for(int32_t lineIndex = lineIndexRangeMin; lineIndex < lineIndexRangeMax; ++lineIndex) - { - BEGIN_GLYPH_SCANLINE_DECODE(data); - - for(int32_t index = indexRangeMin; index < indexRangeMax; ++index) - { - const int32_t xOffsetIndex = xOffset + index; - - // Update the alpha channel. - const uint8_t alpha = *(glyphScanline + index * glyphPixelSize + glyphAlphaIndex); - - // Copy non-transparent pixels only - if(alpha > 0u) - { - // Check alpha of overlapped pixels - uint8_t& currentAlpha = *(bitmapBuffer + xOffsetIndex); - - // For any pixel overlapped with the pixel in previous glyphs, make sure we don't - // overwrite a previous bigger alpha with a smaller alpha (in order to avoid - // semi-transparent gaps between joint glyphs with overlapped pixels, which could - // happen, for example, in the RTL text when we copy glyphs from right to left). - currentAlpha = std::max(currentAlpha, alpha); - } - } - - bitmapBuffer += data.width; - - END_GLYPH_SCANLINE_DECODE(data); - } - - END_GLYPH_BITMAP(); - } - } -} - -/// Draws the specified underline color to the buffer -void DrawUnderline( - const uint32_t bufferWidth, - const uint32_t bufferHeight, - GlyphData& glyphData, - const float baseline, - const float currentUnderlinePosition, - const float maxUnderlineHeight, - const float lineExtentLeft, - const float lineExtentRight, - const UnderlineStyleProperties& commonUnderlineProperties, - const UnderlineStyleProperties& currentUnderlineProperties, - const LineRun& line) -{ - const Vector4& underlineColor = currentUnderlineProperties.colorDefined ? currentUnderlineProperties.color : commonUnderlineProperties.color; - const Text::Underline::Type underlineType = currentUnderlineProperties.typeDefined ? currentUnderlineProperties.type : commonUnderlineProperties.type; - const float dashedUnderlineWidth = currentUnderlineProperties.dashWidthDefined ? currentUnderlineProperties.dashWidth : commonUnderlineProperties.dashWidth; - const float dashedUnderlineGap = currentUnderlineProperties.dashGapDefined ? currentUnderlineProperties.dashGap : commonUnderlineProperties.dashGap; - - int32_t underlineYOffset = glyphData.verticalOffset + baseline + currentUnderlinePosition; - - const uint32_t yRangeMin = underlineYOffset; - const uint32_t yRangeMax = std::min(bufferHeight, underlineYOffset + static_cast(maxUnderlineHeight)); - const uint32_t xRangeMin = static_cast(glyphData.horizontalOffset + lineExtentLeft); - const uint32_t xRangeMax = std::min(bufferWidth, static_cast(glyphData.horizontalOffset + lineExtentRight + 1)); // Due to include last point, we add 1 here - - // If current glyph don't need to be rendered, just ignore. - if((underlineType != Text::Underline::DOUBLE && yRangeMax <= yRangeMin) || xRangeMax <= xRangeMin) - { - return; - } - - // We can optimize by memset when underlineColor.a is near zero - uint8_t underlineColorAlpha = static_cast(underlineColor.a * 255.f); - - uint32_t* bitmapBuffer = reinterpret_cast(glyphData.bitmapBuffer.GetBuffer()); - - // Skip yRangeMin line. - bitmapBuffer += yRangeMin * glyphData.width; - - // Note if underlineType is DASHED, we cannot setup color by memset. - if(underlineType != Text::Underline::DASHED && underlineColorAlpha == 0) - { - for(uint32_t y = yRangeMin; y < yRangeMax; y++) - { - // We can use memset. - memset(bitmapBuffer + xRangeMin, 0, (xRangeMax - xRangeMin) * sizeof(uint32_t)); - bitmapBuffer += glyphData.width; - } - if(underlineType == Text::Underline::DOUBLE) - { - int32_t secondUnderlineYOffset = underlineYOffset - ONE_AND_A_HALF * maxUnderlineHeight; - const uint32_t secondYRangeMin = static_cast(std::max(0, secondUnderlineYOffset)); - const uint32_t secondYRangeMax = static_cast(std::max(0, std::min(static_cast(bufferHeight), secondUnderlineYOffset + static_cast(maxUnderlineHeight)))); - - // Rewind bitmapBuffer pointer, and skip secondYRangeMin line. - bitmapBuffer = reinterpret_cast(glyphData.bitmapBuffer.GetBuffer()) + yRangeMin * glyphData.width; - - for(uint32_t y = secondYRangeMin; y < secondYRangeMax; y++) - { - // We can use memset. - memset(bitmapBuffer + xRangeMin, 0, (xRangeMax - xRangeMin) * sizeof(uint32_t)); - bitmapBuffer += glyphData.width; - } - } - } - else - { - uint32_t packedUnderlineColor = 0u; - uint8_t* packedUnderlineColorBuffer = reinterpret_cast(&packedUnderlineColor); - - // Write the color to the pixel buffer - *(packedUnderlineColorBuffer + 3u) = underlineColorAlpha; - *(packedUnderlineColorBuffer + 2u) = static_cast(underlineColor.b * underlineColorAlpha); - *(packedUnderlineColorBuffer + 1u) = static_cast(underlineColor.g * underlineColorAlpha); - *(packedUnderlineColorBuffer) = static_cast(underlineColor.r * underlineColorAlpha); - - for(uint32_t y = yRangeMin; y < yRangeMax; y++) - { - if(underlineType == Text::Underline::DASHED) - { - float dashWidth = dashedUnderlineWidth; - float dashGap = 0; - - for(uint32_t x = xRangeMin; x < xRangeMax; x++) - { - if(Dali::EqualsZero(dashGap) && dashWidth > 0) - { - // Note : this is same logic as bitmap[y][x] = underlineColor; - *(bitmapBuffer + x) = packedUnderlineColor; - dashWidth--; - } - else if(dashGap < dashedUnderlineGap) - { - dashGap++; - } - else - { - //reset - dashWidth = dashedUnderlineWidth; - dashGap = 0; - } - } - } - else - { - for(uint32_t x = xRangeMin; x < xRangeMax; x++) - { - // Note : this is same logic as bitmap[y][x] = underlineColor; - *(bitmapBuffer + x) = packedUnderlineColor; - } - } - bitmapBuffer += glyphData.width; - } - if(underlineType == Text::Underline::DOUBLE) - { - int32_t secondUnderlineYOffset = underlineYOffset - ONE_AND_A_HALF * maxUnderlineHeight; - const uint32_t secondYRangeMin = static_cast(std::max(0, secondUnderlineYOffset)); - const uint32_t secondYRangeMax = static_cast(std::max(0, std::min(static_cast(bufferHeight), secondUnderlineYOffset + static_cast(maxUnderlineHeight)))); - - // Rewind bitmapBuffer pointer, and skip secondYRangeMin line. - bitmapBuffer = reinterpret_cast(glyphData.bitmapBuffer.GetBuffer()) + yRangeMin * glyphData.width; - - for(uint32_t y = secondYRangeMin; y < secondYRangeMax; y++) - { - for(uint32_t x = xRangeMin; x < xRangeMax; x++) - { - // Note : this is same logic as bitmap[y][x] = underlineColor; - *(bitmapBuffer + x) = packedUnderlineColor; - } - bitmapBuffer += glyphData.width; - } - } - } -} - -/// Draws the background color to the buffer -void DrawBackgroundColor( - Vector4 backgroundColor, - const uint32_t bufferWidth, - const uint32_t bufferHeight, - GlyphData& glyphData, - const float baseline, - const LineRun& line, - const float lineExtentLeft, - const float lineExtentRight) -{ - const int32_t yRangeMin = std::max(0, static_cast(glyphData.verticalOffset + baseline - line.ascender)); - const int32_t yRangeMax = std::min(static_cast(bufferHeight), static_cast(glyphData.verticalOffset + baseline - line.descender)); - const int32_t xRangeMin = std::max(0, static_cast(glyphData.horizontalOffset + lineExtentLeft)); - const int32_t xRangeMax = std::min(static_cast(bufferWidth), static_cast(glyphData.horizontalOffset + lineExtentRight + 1)); // Due to include last point, we add 1 here - - // If current glyph don't need to be rendered, just ignore. - if(yRangeMax <= yRangeMin || xRangeMax <= xRangeMin) - { - return; - } - - // We can optimize by memset when backgroundColor.a is near zero - uint8_t backgroundColorAlpha = static_cast(backgroundColor.a * 255.f); - - uint32_t* bitmapBuffer = reinterpret_cast(glyphData.bitmapBuffer.GetBuffer()); - - // Skip yRangeMin line. - bitmapBuffer += yRangeMin * glyphData.width; - - if(backgroundColorAlpha == 0) - { - for(int32_t y = yRangeMin; y < yRangeMax; y++) - { - // We can use memset. - memset(bitmapBuffer + xRangeMin, 0, (xRangeMax - xRangeMin) * sizeof(uint32_t)); - bitmapBuffer += glyphData.width; - } - } - else - { - uint32_t packedBackgroundColor = 0u; - uint8_t* packedBackgroundColorBuffer = reinterpret_cast(&packedBackgroundColor); - - // Write the color to the pixel buffer - *(packedBackgroundColorBuffer + 3u) = backgroundColorAlpha; - *(packedBackgroundColorBuffer + 2u) = static_cast(backgroundColor.b * backgroundColorAlpha); - *(packedBackgroundColorBuffer + 1u) = static_cast(backgroundColor.g * backgroundColorAlpha); - *(packedBackgroundColorBuffer) = static_cast(backgroundColor.r * backgroundColorAlpha); - - for(int32_t y = yRangeMin; y < yRangeMax; y++) - { - for(int32_t x = xRangeMin; x < xRangeMax; x++) - { - // Note : this is same logic as bitmap[y][x] = backgroundColor; - *(bitmapBuffer + x) = packedBackgroundColor; - } - bitmapBuffer += glyphData.width; - } - } -} - -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) -{ - // Retrieve lines, glyphs, positions and colors from the view model. - const Length modelNumberOfLines = model->GetNumberOfLines(); - const LineRun* const modelLinesBuffer = model->GetLines(); - const Length numberOfGlyphs = model->GetNumberOfGlyphs(); - const GlyphInfo* const glyphsBuffer = model->GetGlyphs(); - const Vector2* const positionBuffer = model->GetLayout(); - const Vector4* const backgroundColorsBuffer = model->GetBackgroundColors(); - const ColorIndex* const backgroundColorIndicesBuffer = model->GetBackgroundColorIndices(); - const bool removeFrontInset = model->IsRemoveFrontInset(); - const bool removeBackInset = model->IsRemoveBackInset(); - - const DevelText::VerticalLineAlignment::Type verLineAlign = model->GetVerticalLineAlignment(); - - // Create and initialize the pixel buffer. - GlyphData glyphData; - glyphData.verticalOffset = verticalOffset; - glyphData.width = bufferWidth; - glyphData.height = bufferHeight; - glyphData.bitmapBuffer = buffer; - glyphData.horizontalOffset = 0; - - ColorIndex prevBackgroundColorIndex = 0; - ColorIndex backgroundColorIndex = 0; - - // Traverses the lines of the text. - for(LineIndex lineIndex = 0u; lineIndex < modelNumberOfLines; ++lineIndex) - { - const LineRun& line = *(modelLinesBuffer + lineIndex); - - // Sets the horizontal offset of the line. - glyphData.horizontalOffset = ignoreHorizontalAlignment ? 0 : static_cast(line.alignmentOffset); - glyphData.horizontalOffset += horizontalOffset; - - // Increases the vertical offset with the line's ascender. - glyphData.verticalOffset += static_cast(line.ascender + GetPreOffsetVerticalLineAlignment(line, verLineAlign)); - - float left = bufferWidth; - float right = 0.0f; - float baseline = 0.0f; - - // Traverses the glyphs of the line. - const GlyphIndex endGlyphIndex = std::min(numberOfGlyphs, line.glyphRun.glyphIndex + line.glyphRun.numberOfGlyphs); - for(GlyphIndex glyphIndex = line.glyphRun.glyphIndex; glyphIndex < endGlyphIndex; ++glyphIndex) - { - // Retrieve the glyph's info. - const GlyphInfo* const glyphInfo = glyphsBuffer + glyphIndex; - - if((glyphInfo->width < Math::MACHINE_EPSILON_1000) || - (glyphInfo->height < Math::MACHINE_EPSILON_1000)) - { - // Nothing to do if default background color, the glyph's width or height is zero. - continue; - } - - backgroundColorIndex = (nullptr == backgroundColorsBuffer) ? 0u : *(backgroundColorIndicesBuffer + glyphIndex); - - if((backgroundColorIndex != prevBackgroundColorIndex) && - (prevBackgroundColorIndex != 0u)) - { - const Vector4& backgroundColor = *(backgroundColorsBuffer + prevBackgroundColorIndex - 1u); - DrawBackgroundColor(backgroundColor, bufferWidth, bufferHeight, glyphData, baseline, line, left, right); - } - - if(backgroundColorIndex == 0u) - { - prevBackgroundColorIndex = backgroundColorIndex; - //if background color is the default do nothing - continue; - } - - // Retrieves the glyph's position. - const Vector2* const position = positionBuffer + glyphIndex; - - if(baseline < position->y + glyphInfo->yBearing) - { - baseline = position->y + glyphInfo->yBearing; - } - - // Calculate the positions of leftmost and rightmost glyphs in the current line - if(removeFrontInset) - { - if((position->x < left) || (backgroundColorIndex != prevBackgroundColorIndex)) - { - left = position->x; - } - } - else - { - const float originPositionLeft = position->x - glyphInfo->xBearing; - if((originPositionLeft < left) || (backgroundColorIndex != prevBackgroundColorIndex)) - { - left = originPositionLeft; - } - } - - if(removeBackInset) - { - if(position->x + glyphInfo->width > right) - { - right = position->x + glyphInfo->width; - } - } - else - { - const float originPositionRight = position->x - glyphInfo->xBearing + glyphInfo->advance; - if(originPositionRight > right) - { - right = originPositionRight; - } - } - - prevBackgroundColorIndex = backgroundColorIndex; - } - - //draw last background at line end if not default - if(backgroundColorIndex != 0u) - { - const Vector4& backgroundColor = *(backgroundColorsBuffer + backgroundColorIndex - 1u); - DrawBackgroundColor(backgroundColor, bufferWidth, bufferHeight, glyphData, baseline, line, left, right); - } - - // Increases the vertical offset with the line's descender. - glyphData.verticalOffset += static_cast(-line.descender + GetPostOffsetVerticalLineAlignment(line, verLineAlign)); - } - - return glyphData.bitmapBuffer; -} - -/// Draws the specified strikethrough color to the buffer -void DrawStrikethrough(const uint32_t bufferWidth, - const uint32_t bufferHeight, - GlyphData& glyphData, - const float baseline, - const float strikethroughStartingYPosition, - const float maxStrikethroughHeight, - const float lineExtentLeft, - const float lineExtentRight, - const StrikethroughStyleProperties& commonStrikethroughProperties, - const StrikethroughStyleProperties& currentStrikethroughProperties, - const LineRun& line) -{ - const Vector4& strikethroughColor = currentStrikethroughProperties.colorDefined ? currentStrikethroughProperties.color : commonStrikethroughProperties.color; - - const uint32_t yRangeMin = static_cast(strikethroughStartingYPosition); - const uint32_t yRangeMax = std::min(bufferHeight, static_cast(strikethroughStartingYPosition + maxStrikethroughHeight)); - const uint32_t xRangeMin = static_cast(glyphData.horizontalOffset + lineExtentLeft); - const uint32_t xRangeMax = std::min(bufferWidth, static_cast(glyphData.horizontalOffset + lineExtentRight + 1)); // Due to include last point, we add 1 here - - // If current glyph don't need to be rendered, just ignore. - if(yRangeMax <= yRangeMin || xRangeMax <= xRangeMin) - { - return; - } - - // We can optimize by memset when strikethroughColor.a is near zero - uint8_t strikethroughColorAlpha = static_cast(strikethroughColor.a * 255.f); - - uint32_t* bitmapBuffer = reinterpret_cast(glyphData.bitmapBuffer.GetBuffer()); - - // Skip yRangeMin line. - bitmapBuffer += yRangeMin * glyphData.width; - - if(strikethroughColorAlpha == 0) - { - for(uint32_t y = yRangeMin; y < yRangeMax; y++) - { - // We can use memset. - memset(bitmapBuffer + xRangeMin, 0, (xRangeMax - xRangeMin) * sizeof(uint32_t)); - bitmapBuffer += glyphData.width; - } - } - else - { - uint32_t packedStrikethroughColor = 0u; - uint8_t* packedStrikethroughColorBuffer = reinterpret_cast(&packedStrikethroughColor); - - // Write the color to the pixel buffer - *(packedStrikethroughColorBuffer + 3u) = strikethroughColorAlpha; - *(packedStrikethroughColorBuffer + 2u) = static_cast(strikethroughColor.b * strikethroughColorAlpha); - *(packedStrikethroughColorBuffer + 1u) = static_cast(strikethroughColor.g * strikethroughColorAlpha); - *(packedStrikethroughColorBuffer) = static_cast(strikethroughColor.r * strikethroughColorAlpha); - - for(uint32_t y = yRangeMin; y < yRangeMax; y++) - { - for(uint32_t x = xRangeMin; x < xRangeMax; x++) - { - // Note : this is same logic as bitmap[y][x] = strikethroughColor; - *(bitmapBuffer + x) = packedStrikethroughColor; - } - bitmapBuffer += glyphData.width; - } - } -} - -/** - * @brief Create an initialized image buffer filled with transparent color. - * - * Creates the pixel data used to generate the final image with the given size. - * - * @param[in] bufferWidth The width of the image buffer. - * @param[in] bufferHeight The height of the image buffer. - * @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). - * - * @return An image buffer. - */ -inline Devel::PixelBuffer CreateTransparentImageBuffer(const uint32_t bufferWidth, const uint32_t bufferHeight, const Pixel::Format pixelFormat) -{ - Devel::PixelBuffer imageBuffer = Devel::PixelBuffer::New(bufferWidth, bufferHeight, pixelFormat); - - if(Pixel::RGBA8888 == pixelFormat) - { - const uint32_t bufferSizeInt = bufferWidth * bufferHeight; - const size_t bufferSizeChar = sizeof(uint32_t) * static_cast(bufferSizeInt); - memset(imageBuffer.GetBuffer(), 0, bufferSizeChar); - } - else - { - memset(imageBuffer.GetBuffer(), 0, static_cast(bufferWidth * bufferHeight)); - } - - return imageBuffer; + return ((res + ((res + 257) >> 8)) >> 8); // fast divide by 255. } /** @@ -845,13 +104,13 @@ void CombineImageBuffer(Devel::PixelBuffer& __restrict__ topPixelBuffer, Devel:: uint32_t* topBuffer = reinterpret_cast(topPixelBuffer.GetBuffer()); uint32_t* bottomBuffer = reinterpret_cast(bottomPixelBuffer.GetBuffer()); - if(topBuffer == NULL && bottomBuffer == NULL) + if(topBuffer == nullptr && bottomBuffer == nullptr) { // Nothing to do if both buffers are empty. return; } - if(topBuffer == NULL) + if(topBuffer == nullptr) { // Nothing to do if topBuffer is empty. // If we need to store the result into top, change topPixelBuffer as bottomPixelBuffer. @@ -862,7 +121,7 @@ void CombineImageBuffer(Devel::PixelBuffer& __restrict__ topPixelBuffer, Devel:: return; } - if(bottomBuffer == NULL) + if(bottomBuffer == nullptr) { // Nothing to do if bottomBuffer is empty. // If we need to store the result into bottom, change bottomPixelBuffer as topPixelBuffer. @@ -934,18 +193,18 @@ TypesetterPtr Typesetter::New(const ModelInterface* const model) ViewModel* Typesetter::GetViewModel() { - return mModel; + return mImpl->GetViewModel(); } void Typesetter::SetFontClient(TextAbstraction::FontClient& fontClient) { - mFontClient = fontClient; + mImpl->SetFontClient(fontClient); } PixelData Typesetter::Render(const Vector2& size, Toolkit::DevelText::TextDirection::Type textDirection, RenderBehaviour behaviour, bool ignoreHorizontalAlignment, Pixel::Format pixelFormat) { - Devel::PixelBuffer result = RenderWithPixelBuffer(size, textDirection, behaviour, ignoreHorizontalAlignment, pixelFormat); - PixelData pixelData = Devel::PixelBuffer::Convert(result); + Devel::PixelBuffer result = RenderWithPixelBuffer(size, textDirection, behaviour, ignoreHorizontalAlignment, pixelFormat); + PixelData pixelData = Devel::PixelBuffer::Convert(result); return pixelData; } @@ -965,16 +224,19 @@ Devel::PixelBuffer Typesetter::RenderWithPixelBuffer(const Vector2& size, Toolki DALI_TRACE_SCOPE(gTraceFilter, "DALI_TEXT_RENDERING_TYPESETTER"); // @todo. This initial implementation for a TextLabel has only one visible page. + // Use l-value to make ensure it is not nullptr, so compiler happy. + auto& viewModel = *(mImpl->GetViewModel()); + // Elides the text if needed. - mModel->ElideGlyphs(mFontClient); + viewModel.ElideGlyphs(mImpl->GetFontClient()); // Retrieves the layout size. - const Size& layoutSize = mModel->GetLayoutSize(); - const int32_t outlineWidth = static_cast(mModel->GetOutlineWidth()); + const Size& layoutSize = viewModel.GetLayoutSize(); + const int32_t outlineWidth = static_cast(viewModel.GetOutlineWidth()); // Set the offset for the horizontal alignment according to the text direction and outline width. int32_t penX = 0; - switch(mModel->GetHorizontalAlignment()) + switch(viewModel.GetHorizontalAlignment()) { case HorizontalAlignment::BEGIN: { @@ -995,7 +257,7 @@ Devel::PixelBuffer Typesetter::RenderWithPixelBuffer(const Vector2& size, Toolki // Set the offset for the vertical alignment. int32_t penY = 0u; - switch(mModel->GetVerticalAlignment()) + switch(viewModel.GetVerticalAlignment()) { case VerticalAlignment::TOP: { @@ -1016,12 +278,12 @@ Devel::PixelBuffer Typesetter::RenderWithPixelBuffer(const Vector2& size, Toolki } } - const bool isCutoutEnabled = mModel->IsCutoutEnabled(); + const bool isCutoutEnabled = viewModel.IsCutoutEnabled(); if(isCutoutEnabled) { - Vector2 offset = mModel->GetOffsetWithCutout(); - penX = offset.x; - penY = offset.y; + Vector2 offset = viewModel.GetOffsetWithCutout(); + penX = offset.x; + penY = offset.y; } // Generate the image buffers of the text for each different style first, @@ -1036,15 +298,15 @@ Devel::PixelBuffer Typesetter::RenderWithPixelBuffer(const Vector2& size, Toolki const size_t bufferSizeChar = sizeof(uint32_t) * static_cast(bufferSizeInt); //Elided text in ellipsis at START could start on index greater than 0 - auto startIndexOfGlyphs = mModel->GetStartIndexOfElidedGlyphs(); - auto endIndexOfGlyphs = mModel->GetEndIndexOfElidedGlyphs(); + auto startIndexOfGlyphs = viewModel.GetStartIndexOfElidedGlyphs(); + auto endIndexOfGlyphs = viewModel.GetEndIndexOfElidedGlyphs(); Devel::PixelBuffer imageBuffer; if(RENDER_MASK == behaviour) { // Generate the image buffer as an alpha mask for color glyphs. - imageBuffer = CreateImageBuffer(bufferWidth, bufferHeight, Typesetter::STYLE_MASK, ignoreHorizontalAlignment, pixelFormat, penX, penY, startIndexOfGlyphs, endIndexOfGlyphs); + imageBuffer = mImpl->CreateImageBuffer(bufferWidth, bufferHeight, Typesetter::STYLE_MASK, ignoreHorizontalAlignment, pixelFormat, penX, penY, startIndexOfGlyphs, endIndexOfGlyphs); } else if(RENDER_NO_TEXT == behaviour || RENDER_OVERLAY_STYLE == behaviour) { @@ -1055,20 +317,20 @@ Devel::PixelBuffer Typesetter::RenderWithPixelBuffer(const Vector2& size, Toolki else { // Generate the image buffer for the text with no style. - imageBuffer = CreateImageBuffer(bufferWidth, bufferHeight, Typesetter::STYLE_NONE, ignoreHorizontalAlignment, pixelFormat, penX, penY, startIndexOfGlyphs, endIndexOfGlyphs); + imageBuffer = mImpl->CreateImageBuffer(bufferWidth, bufferHeight, Typesetter::STYLE_NONE, ignoreHorizontalAlignment, pixelFormat, penX, penY, startIndexOfGlyphs, endIndexOfGlyphs); } if((RENDER_NO_STYLES != behaviour) && (RENDER_MASK != behaviour)) { // Generate the outline if enabled - const uint16_t outlineWidth = mModel->GetOutlineWidth(); - const float outlineAlpha = mModel->GetOutlineColor().a; + const uint16_t outlineWidth = viewModel.GetOutlineWidth(); + const float outlineAlpha = viewModel.GetOutlineColor().a; if(outlineWidth != 0u && fabsf(outlineAlpha) > Math::MACHINE_EPSILON_1 && RENDER_OVERLAY_STYLE != behaviour) { // Create the image buffer for outline - Devel::PixelBuffer outlineImageBuffer = CreateImageBuffer(bufferWidth, bufferHeight, Typesetter::STYLE_OUTLINE, ignoreHorizontalAlignment, pixelFormat, penX, penY, startIndexOfGlyphs, endIndexOfGlyphs); + Devel::PixelBuffer outlineImageBuffer = mImpl->CreateImageBuffer(bufferWidth, bufferHeight, Typesetter::STYLE_OUTLINE, ignoreHorizontalAlignment, pixelFormat, penX, penY, startIndexOfGlyphs, endIndexOfGlyphs); - const float& blurRadius = mModel->GetOutlineBlurRadius(); + const float& blurRadius = viewModel.GetOutlineBlurRadius(); if(blurRadius > Math::MACHINE_EPSILON_1) { @@ -1082,15 +344,15 @@ Devel::PixelBuffer Typesetter::RenderWithPixelBuffer(const Vector2& size, Toolki // @todo. Support shadow for partial text later on. // Generate the shadow if enabled - const Vector2& shadowOffset = mModel->GetShadowOffset(); - const float shadowAlpha = mModel->GetShadowColor().a; + const Vector2& shadowOffset = viewModel.GetShadowOffset(); + const float shadowAlpha = viewModel.GetShadowColor().a; 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)) { // Create the image buffer for shadow - Devel::PixelBuffer shadowImageBuffer = CreateImageBuffer(bufferWidth, bufferHeight, Typesetter::STYLE_SHADOW, ignoreHorizontalAlignment, pixelFormat, penX, penY, startIndexOfGlyphs, endIndexOfGlyphs); + Devel::PixelBuffer shadowImageBuffer = mImpl->CreateImageBuffer(bufferWidth, bufferHeight, Typesetter::STYLE_SHADOW, ignoreHorizontalAlignment, pixelFormat, penX, penY, startIndexOfGlyphs, endIndexOfGlyphs); // Check whether it will be a soft shadow - const float& blurRadius = mModel->GetShadowBlurRadius(); + const float& blurRadius = viewModel.GetShadowBlurRadius(); if(blurRadius > Math::MACHINE_EPSILON_1) { @@ -1102,24 +364,24 @@ Devel::PixelBuffer Typesetter::RenderWithPixelBuffer(const Vector2& size, Toolki } // Generate the background if enabled - const bool backgroundEnabled = mModel->IsBackgroundEnabled(); - const bool backgroundMarkupSet = mModel->IsMarkupBackgroundColorSet(); + const bool backgroundEnabled = viewModel.IsBackgroundEnabled(); + const bool backgroundMarkupSet = viewModel.IsMarkupBackgroundColorSet(); if((backgroundEnabled || backgroundMarkupSet) && RENDER_OVERLAY_STYLE != behaviour) { Devel::PixelBuffer backgroundImageBuffer; if(backgroundEnabled) { - backgroundImageBuffer = CreateImageBuffer(bufferWidth, bufferHeight, Typesetter::STYLE_BACKGROUND, ignoreHorizontalAlignment, pixelFormat, penX, penY, startIndexOfGlyphs, endIndexOfGlyphs); + backgroundImageBuffer = mImpl->CreateImageBuffer(bufferWidth, bufferHeight, Typesetter::STYLE_BACKGROUND, ignoreHorizontalAlignment, pixelFormat, penX, penY, startIndexOfGlyphs, endIndexOfGlyphs); } else { - backgroundImageBuffer = CreateTransparentImageBuffer(bufferWidth, bufferHeight, pixelFormat); + backgroundImageBuffer = Impl::CreateTransparentImageBuffer(bufferWidth, bufferHeight, pixelFormat); } if(backgroundMarkupSet) { - DrawGlyphsBackground(mModel, backgroundImageBuffer, bufferWidth, bufferHeight, ignoreHorizontalAlignment, penX, penY); + mImpl->DrawGlyphsBackground(backgroundImageBuffer, bufferWidth, bufferHeight, ignoreHorizontalAlignment, penX, penY); } // Combine the two buffers @@ -1127,12 +389,12 @@ Devel::PixelBuffer Typesetter::RenderWithPixelBuffer(const Vector2& size, Toolki } // Generate the background_with_mask if enabled - const bool backgroundWithCutoutEnabled = mModel->IsBackgroundWithCutoutEnabled(); + const bool backgroundWithCutoutEnabled = viewModel.IsBackgroundWithCutoutEnabled(); if((backgroundWithCutoutEnabled) && RENDER_OVERLAY_STYLE != behaviour) { Devel::PixelBuffer backgroundImageBuffer; - backgroundImageBuffer = CreateFullBackgroundBuffer(bufferWidth, bufferHeight, mModel->GetBackgroundColorWithCutout()); + backgroundImageBuffer = CreateFullBackgroundBuffer(bufferWidth, bufferHeight, viewModel.GetBackgroundColorWithCutout()); // Combine the two buffers CombineImageBuffer(imageBuffer, backgroundImageBuffer, bufferWidth, bufferHeight, true); @@ -1140,33 +402,33 @@ Devel::PixelBuffer Typesetter::RenderWithPixelBuffer(const Vector2& size, Toolki if(RENDER_OVERLAY_STYLE == behaviour) { - if(mModel->IsUnderlineEnabled()) + if(viewModel.IsUnderlineEnabled()) { // Create the image buffer for underline - Devel::PixelBuffer underlineImageBuffer = CreateImageBuffer(bufferWidth, bufferHeight, Typesetter::STYLE_UNDERLINE, ignoreHorizontalAlignment, pixelFormat, penX, penY, startIndexOfGlyphs, endIndexOfGlyphs); + Devel::PixelBuffer underlineImageBuffer = mImpl->CreateImageBuffer(bufferWidth, bufferHeight, Typesetter::STYLE_UNDERLINE, ignoreHorizontalAlignment, pixelFormat, penX, penY, startIndexOfGlyphs, endIndexOfGlyphs); // Combine the two buffers CombineImageBuffer(imageBuffer, underlineImageBuffer, bufferWidth, bufferHeight, true); } - if(mModel->IsStrikethroughEnabled()) + if(viewModel.IsStrikethroughEnabled()) { // Create the image buffer for strikethrough - Devel::PixelBuffer strikethroughImageBuffer = CreateImageBuffer(bufferWidth, bufferHeight, Typesetter::STYLE_STRIKETHROUGH, ignoreHorizontalAlignment, pixelFormat, penX, penY, 0u, endIndexOfGlyphs); + Devel::PixelBuffer strikethroughImageBuffer = mImpl->CreateImageBuffer(bufferWidth, bufferHeight, Typesetter::STYLE_STRIKETHROUGH, ignoreHorizontalAlignment, pixelFormat, penX, penY, 0u, endIndexOfGlyphs); // Combine the two buffers CombineImageBuffer(imageBuffer, strikethroughImageBuffer, bufferWidth, bufferHeight, true); } // Markup-Processor for overlay styles - if(mModel->IsMarkupProcessorEnabled() || mModel->IsSpannedTextPlaced()) + if(viewModel.IsMarkupProcessorEnabled() || viewModel.IsSpannedTextPlaced()) { - if(mModel->IsMarkupUnderlineSet()) + if(viewModel.IsMarkupUnderlineSet()) { imageBuffer = ApplyUnderlineMarkupImageBuffer(imageBuffer, bufferWidth, bufferHeight, ignoreHorizontalAlignment, pixelFormat, penX, penY); } - if(mModel->IsMarkupStrikethroughSet()) + if(viewModel.IsMarkupStrikethroughSet()) { imageBuffer = ApplyStrikethroughMarkupImageBuffer(imageBuffer, bufferWidth, bufferHeight, ignoreHorizontalAlignment, pixelFormat, penX, penY); } @@ -1179,8 +441,8 @@ Devel::PixelBuffer Typesetter::RenderWithPixelBuffer(const Vector2& size, Toolki Devel::PixelBuffer Typesetter::CreateFullBackgroundBuffer(const uint32_t bufferWidth, const uint32_t bufferHeight, const Vector4& backgroundColor) { - const uint32_t bufferSizeInt = bufferWidth * bufferHeight; - uint8_t backgroundColorAlpha = static_cast(backgroundColor.a * 255.f); + const uint32_t bufferSizeInt = bufferWidth * bufferHeight; + uint8_t backgroundColorAlpha = static_cast(backgroundColor.a * 255.f); Devel::PixelBuffer buffer = Devel::PixelBuffer::New(bufferWidth, bufferHeight, Pixel::RGBA8888); @@ -1200,412 +462,17 @@ Devel::PixelBuffer Typesetter::CreateFullBackgroundBuffer(const uint32_t bufferW return buffer; } -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) -{ - // Retrieve lines, glyphs, positions and colors from the view model. - const Length modelNumberOfLines = mModel->GetNumberOfLines(); - const LineRun* const __restrict__ modelLinesBuffer = mModel->GetLines(); - const GlyphInfo* const __restrict__ glyphsBuffer = mModel->GetGlyphs(); - const Vector2* const __restrict__ positionBuffer = mModel->GetLayout(); - const Vector4* const __restrict__ colorsBuffer = mModel->GetColors(); - const ColorIndex* const __restrict__ colorIndexBuffer = mModel->GetColorIndices(); - const GlyphInfo* __restrict__ hyphens = mModel->GetHyphens(); - const Length* __restrict__ hyphenIndices = mModel->GetHyphenIndices(); - const Length hyphensCount = mModel->GetHyphensCount(); - const bool removeFrontInset = mModel->IsRemoveFrontInset(); - const bool removeBackInset = mModel->IsRemoveBackInset(); - const bool cutoutEnabled = mModel->IsCutoutEnabled(); - - // Elided text info. Indices according to elided text and Ellipsis position. - const auto startIndexOfGlyphs = mModel->GetStartIndexOfElidedGlyphs(); - const auto endIndexOfGlyphs = mModel->GetEndIndexOfElidedGlyphs(); - const auto firstMiddleIndexOfElidedGlyphs = mModel->GetFirstMiddleIndexOfElidedGlyphs(); - const auto secondMiddleIndexOfElidedGlyphs = mModel->GetSecondMiddleIndexOfElidedGlyphs(); - const auto ellipsisPosition = mModel->GetEllipsisPosition(); - - // Whether to use the default color. - const bool useDefaultColor = (NULL == colorsBuffer); - const Vector4& defaultColor = mModel->GetDefaultColor(); - - // Create and initialize the pixel buffer. - GlyphData glyphData; - glyphData.verticalOffset = verticalOffset; - glyphData.width = bufferWidth; - glyphData.height = bufferHeight; - glyphData.bitmapBuffer = CreateTransparentImageBuffer(bufferWidth, bufferHeight, pixelFormat); - glyphData.horizontalOffset = 0; - - // Get a handle of the font client. Used to retrieve the bitmaps of the glyphs. - Length hyphenIndex = 0; - - const Character* __restrict__ textBuffer = mModel->GetTextBuffer(); - float calculatedAdvance = 0.f; - const Vector& __restrict__ glyphToCharacterMap = mModel->GetGlyphsToCharacters(); - const CharacterIndex* __restrict__ glyphToCharacterMapBuffer = glyphToCharacterMap.Begin(); - - const DevelText::VerticalLineAlignment::Type verLineAlign = mModel->GetVerticalLineAlignment(); - - // Traverses the lines of the text. - for(LineIndex lineIndex = 0u; lineIndex < modelNumberOfLines; ++lineIndex) - { - const LineRun& line = *(modelLinesBuffer + lineIndex); - - // Sets the horizontal offset of the line. - glyphData.horizontalOffset = ignoreHorizontalAlignment ? 0 : static_cast(line.alignmentOffset); - glyphData.horizontalOffset += horizontalOffset; - - // Increases the vertical offset with the line's ascender. - glyphData.verticalOffset += static_cast(line.ascender + GetPreOffsetVerticalLineAlignment(line, verLineAlign)); - - // Retrieves the glyph's outline width - float outlineWidth = static_cast(mModel->GetOutlineWidth()); - - if(style == Typesetter::STYLE_OUTLINE) - { - const Vector2& outlineOffset = mModel->GetOutlineOffset(); - - glyphData.horizontalOffset -= outlineWidth; - glyphData.horizontalOffset += outlineOffset.x; - if(lineIndex == 0u) - { - // Only need to add the vertical outline offset for the first line - glyphData.verticalOffset -= outlineWidth; - glyphData.verticalOffset += outlineOffset.y; - } - } - else if(style == Typesetter::STYLE_SHADOW) - { - const Vector2& shadowOffset = mModel->GetShadowOffset(); - glyphData.horizontalOffset += shadowOffset.x - outlineWidth; // if outline enabled then shadow should offset from outline - - if(lineIndex == 0u) - { - // Only need to add the vertical shadow offset for first line - glyphData.verticalOffset += shadowOffset.y - outlineWidth; - } - } - - const bool underlineEnabled = mModel->IsUnderlineEnabled(); - const bool strikethroughEnabled = mModel->IsStrikethroughEnabled(); - const float modelCharacterSpacing = mModel->GetCharacterSpacing(); - - // Get the character-spacing runs. - const Vector& __restrict__ characterSpacingGlyphRuns = mModel->GetCharacterSpacingGlyphRuns(); - - // Aggregate underline-style-properties from mModel - const UnderlineStyleProperties modelUnderlineProperties{mModel->GetUnderlineType(), - mModel->GetUnderlineColor(), - mModel->GetUnderlineHeight(), - mModel->GetDashedUnderlineGap(), - mModel->GetDashedUnderlineWidth(), - true, - true, - true, - true, - true}; - - // Aggregate strikethrough-style-properties from mModel - const StrikethroughStyleProperties modelStrikethroughProperties{mModel->GetStrikethroughColor(), - mModel->GetStrikethroughHeight(), - true, - true}; - - // Get the underline runs. - const Length numberOfUnderlineRuns = mModel->GetNumberOfUnderlineRuns(); - Vector underlineRuns; - underlineRuns.Resize(numberOfUnderlineRuns); - mModel->GetUnderlineRuns(underlineRuns.Begin(), 0u, numberOfUnderlineRuns); - - // Get the strikethrough runs. - const Length numberOfStrikethroughRuns = mModel->GetNumberOfStrikethroughRuns(); - Vector strikethroughRuns; - strikethroughRuns.Resize(numberOfStrikethroughRuns); - mModel->GetStrikethroughRuns(strikethroughRuns.Begin(), 0u, numberOfStrikethroughRuns); - - bool thereAreUnderlinedGlyphs = false; - bool thereAreStrikethroughGlyphs = false; - - float currentUnderlinePosition = 0.0f; - float currentUnderlineHeight = modelUnderlineProperties.height; - float maxUnderlineHeight = currentUnderlineHeight; - auto currentUnderlineProperties = modelUnderlineProperties; - - float currentStrikethroughHeight = modelStrikethroughProperties.height; - float maxStrikethroughHeight = currentStrikethroughHeight; - auto currentStrikethroughProperties = modelStrikethroughProperties; - float strikethroughStartingYPosition = 0.0f; - - FontId lastFontId = 0; - - float lineExtentLeft = bufferWidth; - float lineExtentRight = 0.0f; - float baseline = 0.0f; - bool addHyphen = false; - - // Traverses the glyphs of the line. - const GlyphIndex startGlyphIndex = std::max(std::max(line.glyphRun.glyphIndex, startIndexOfGlyphs), fromGlyphIndex); - GlyphIndex endGlyphIndex = (line.isSplitToTwoHalves ? line.glyphRunSecondHalf.glyphIndex + line.glyphRunSecondHalf.numberOfGlyphs : line.glyphRun.glyphIndex + line.glyphRun.numberOfGlyphs) - 1u; - endGlyphIndex = std::min(std::min(endGlyphIndex, endIndexOfGlyphs), toGlyphIndex); - - for(GlyphIndex glyphIndex = startGlyphIndex; glyphIndex <= endGlyphIndex; ++glyphIndex) - { - //To handle START case of ellipsis, the first glyph has been shifted - //glyphIndex represent indices in whole glyphs but elidedGlyphIndex represents indices in elided Glyphs - GlyphIndex elidedGlyphIndex = glyphIndex - startIndexOfGlyphs; - - //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. - if(ellipsisPosition == DevelText::EllipsisPosition::MIDDLE) - { - if(glyphIndex > firstMiddleIndexOfElidedGlyphs && - glyphIndex < secondMiddleIndexOfElidedGlyphs) - { - // Ignore any glyph that removed for MIDDLE ellipsis - continue; - } - if(glyphIndex >= secondMiddleIndexOfElidedGlyphs) - { - elidedGlyphIndex -= (secondMiddleIndexOfElidedGlyphs - firstMiddleIndexOfElidedGlyphs - 1u); - } - } - - // Retrieve the glyph's info. - const GlyphInfo* glyphInfo; - - if(addHyphen && hyphens) - { - glyphInfo = hyphens + hyphenIndex; - hyphenIndex++; - } - else - { - glyphInfo = glyphsBuffer + elidedGlyphIndex; - } - - if((glyphInfo->width < Math::MACHINE_EPSILON_1000) || - (glyphInfo->height < Math::MACHINE_EPSILON_1000)) - { - // Nothing to do if the glyph's width or height is zero. - continue; - } - - Vector::ConstIterator currentUnderlinedGlyphRunIt = underlineRuns.End(); - const bool underlineGlyph = underlineEnabled || IsGlyphUnderlined(glyphIndex, underlineRuns, currentUnderlinedGlyphRunIt); - currentUnderlineProperties = GetCurrentUnderlineProperties(glyphIndex, underlineGlyph, underlineRuns, currentUnderlinedGlyphRunIt, modelUnderlineProperties); - currentUnderlineHeight = currentUnderlineProperties.height; - thereAreUnderlinedGlyphs = thereAreUnderlinedGlyphs || underlineGlyph; - - Vector::ConstIterator currentStrikethroughGlyphRunIt = strikethroughRuns.End(); - const bool strikethroughGlyph = strikethroughEnabled || IsGlyphStrikethrough(glyphIndex, strikethroughRuns, currentStrikethroughGlyphRunIt); - currentStrikethroughProperties = GetCurrentStrikethroughProperties(glyphIndex, strikethroughGlyph, strikethroughRuns, currentStrikethroughGlyphRunIt, modelStrikethroughProperties); - currentStrikethroughHeight = currentStrikethroughProperties.height; - thereAreStrikethroughGlyphs = thereAreStrikethroughGlyphs || strikethroughGlyph; - - // Are we still using the same fontId as previous - if((glyphInfo->fontId != lastFontId) && (strikethroughGlyph || underlineGlyph)) - { - // We need to fetch fresh font underline metrics - FontMetrics fontMetrics; - mFontClient.GetFontMetrics(glyphInfo->fontId, fontMetrics); - - //The currentUnderlinePosition will be used for both Underline and/or Strikethrough - currentUnderlinePosition = FetchUnderlinePositionFromFontMetrics(fontMetrics); - - if(underlineGlyph) - { - CalcualteUnderlineHeight(fontMetrics, currentUnderlineHeight, maxUnderlineHeight); - } - - if(strikethroughGlyph) - { - CalcualteStrikethroughHeight(currentStrikethroughHeight, maxStrikethroughHeight); - } - - // Update lastFontId because fontId is changed - lastFontId = glyphInfo->fontId; // Prevents searching for existing blocksizes when string of the same fontId. - } - - // Retrieves the glyph's position. - Vector2 position = *(positionBuffer + elidedGlyphIndex); - - if(addHyphen) - { - GlyphInfo tempInfo = *(glyphsBuffer + elidedGlyphIndex); - const float characterSpacing = GetGlyphCharacterSpacing(glyphIndex, characterSpacingGlyphRuns, modelCharacterSpacing); - calculatedAdvance = GetCalculatedAdvance(*(textBuffer + (*(glyphToCharacterMapBuffer + elidedGlyphIndex))), characterSpacing, tempInfo.advance); - position.x = position.x + calculatedAdvance - tempInfo.xBearing + glyphInfo->xBearing; - position.y = -glyphInfo->yBearing; - } - - if(baseline < position.y + glyphInfo->yBearing) - { - baseline = position.y + glyphInfo->yBearing; - } - - // Calculate the positions of leftmost and rightmost glyphs in the current line - if(removeFrontInset) - { - if(position.x < lineExtentLeft) - { - lineExtentLeft = position.x; - } - } - else - { - const float originPositionLeft = position.x - glyphInfo->xBearing; - if(originPositionLeft < lineExtentLeft) - { - lineExtentLeft = originPositionLeft; - } - } - - if(removeBackInset) - { - if(position.x + glyphInfo->width > lineExtentRight) - { - lineExtentRight = position.x + glyphInfo->width; - } - } - else - { - const float originPositionRight = position.x - glyphInfo->xBearing + glyphInfo->advance; - if(originPositionRight > lineExtentRight) - { - lineExtentRight = originPositionRight; - } - } - - // Retrieves the glyph's color. - const ColorIndex colorIndex = useDefaultColor ? 0u : *(colorIndexBuffer + glyphIndex); - - Vector4 color; - if(style == Typesetter::STYLE_SHADOW) - { - color = mModel->GetShadowColor(); - } - else if(style == Typesetter::STYLE_OUTLINE) - { - color = mModel->GetOutlineColor(); - } - else - { - color = (useDefaultColor || (0u == colorIndex)) ? defaultColor : *(colorsBuffer + (colorIndex - 1u)); - } - - if(style == Typesetter::STYLE_NONE && cutoutEnabled) - { - // Temporarily adjust the transparency to 1.f - color.a = 1.f; - } - - // Premultiply alpha - color.r *= color.a; - color.g *= color.a; - color.b *= color.a; - - // Retrieves the glyph's bitmap. - glyphData.glyphBitmap.buffer = NULL; - glyphData.glyphBitmap.width = glyphInfo->width; // Desired width and height. - glyphData.glyphBitmap.height = glyphInfo->height; - - if(style != Typesetter::STYLE_OUTLINE && style != Typesetter::STYLE_SHADOW) - { - // Don't render outline for other styles - outlineWidth = 0.0f; - } - - if(style != Typesetter::STYLE_UNDERLINE && style != Typesetter::STYLE_STRIKETHROUGH) - { - mFontClient.CreateBitmap(glyphInfo->fontId, - glyphInfo->index, - glyphInfo->isItalicRequired, - glyphInfo->isBoldRequired, - glyphData.glyphBitmap, - static_cast(outlineWidth)); - } - - // Sets the glyph's bitmap into the bitmap of the whole text. - if(NULL != glyphData.glyphBitmap.buffer) - { - if(style == Typesetter::STYLE_OUTLINE) - { - // Set the position offset for the current glyph - glyphData.horizontalOffset -= glyphData.glyphBitmap.outlineOffsetX; - glyphData.verticalOffset -= glyphData.glyphBitmap.outlineOffsetY; - } - - // Set the buffer of the glyph's bitmap into the final bitmap's buffer - TypesetGlyph(glyphData, - &position, - &color, - style, - pixelFormat); - - if(style == Typesetter::STYLE_OUTLINE) - { - // Reset the position offset for the next glyph - glyphData.horizontalOffset += glyphData.glyphBitmap.outlineOffsetX; - glyphData.verticalOffset += glyphData.glyphBitmap.outlineOffsetY; - } - - // free the glyphBitmap.buffer if it is owner of buffer - if(glyphData.glyphBitmap.isBufferOwned) - { - free(glyphData.glyphBitmap.buffer); - glyphData.glyphBitmap.isBufferOwned = false; - } - glyphData.glyphBitmap.buffer = NULL; - } - - if(hyphenIndices) - { - while((hyphenIndex < hyphensCount) && (glyphIndex > hyphenIndices[hyphenIndex])) - { - hyphenIndex++; - } - - addHyphen = ((hyphenIndex < hyphensCount) && ((glyphIndex + 1) == hyphenIndices[hyphenIndex])); - if(addHyphen) - { - glyphIndex--; - } - } - } - - // Draw the underline from the leftmost glyph to the rightmost glyph - if(thereAreUnderlinedGlyphs && style == Typesetter::STYLE_UNDERLINE) - { - DrawUnderline(bufferWidth, bufferHeight, glyphData, baseline, currentUnderlinePosition, maxUnderlineHeight, lineExtentLeft, lineExtentRight, modelUnderlineProperties, currentUnderlineProperties, line); - } - - // Draw the background color from the leftmost glyph to the rightmost glyph - if(style == Typesetter::STYLE_BACKGROUND) - { - DrawBackgroundColor(mModel->GetBackgroundColor(), bufferWidth, bufferHeight, glyphData, baseline, line, lineExtentLeft, lineExtentRight); - } - - // Draw the strikethrough from the leftmost glyph to the rightmost glyph - if(thereAreStrikethroughGlyphs && style == Typesetter::STYLE_STRIKETHROUGH) - { - //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. - 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. - DrawStrikethrough(bufferWidth, bufferHeight, glyphData, baseline, strikethroughStartingYPosition, maxStrikethroughHeight, lineExtentLeft, lineExtentRight, modelStrikethroughProperties, currentStrikethroughProperties, line); - } - - // Increases the vertical offset with the line's descender & line spacing. - glyphData.verticalOffset += static_cast(-line.descender + GetPostOffsetVerticalLineAlignment(line, verLineAlign)); - } - - return glyphData.bitmapBuffer; -} - 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) { + // Use l-value to make ensure it is not nullptr, so compiler happy. + auto& viewModel = *(mImpl->GetViewModel()); + // Underline-tags (this is for Markup case) // Get the underline runs. - const Length numberOfUnderlineRuns = mModel->GetNumberOfUnderlineRuns(); + const Length numberOfUnderlineRuns = viewModel.GetNumberOfUnderlineRuns(); Vector underlineRuns; underlineRuns.Resize(numberOfUnderlineRuns); - mModel->GetUnderlineRuns(underlineRuns.Begin(), 0u, numberOfUnderlineRuns); + viewModel.GetUnderlineRuns(underlineRuns.Begin(), 0u, numberOfUnderlineRuns); // Iterate on the consecutive underlined glyph run and connect them into one chunk of underlined characters. Vector::ConstIterator itGlyphRun = underlineRuns.Begin(); @@ -1619,7 +486,7 @@ Devel::PixelBuffer Typesetter::ApplyUnderlineMarkupImageBuffer(Devel::PixelBuffe endGlyphIndex = startGlyphIndex + itGlyphRun->glyphRun.numberOfGlyphs - 1; // Create the image buffer for underline - Devel::PixelBuffer underlineImageBuffer = CreateImageBuffer(bufferWidth, bufferHeight, Typesetter::STYLE_UNDERLINE, ignoreHorizontalAlignment, pixelFormat, horizontalOffset, verticalOffset, startGlyphIndex, endGlyphIndex); + Devel::PixelBuffer underlineImageBuffer = mImpl->CreateImageBuffer(bufferWidth, bufferHeight, Typesetter::STYLE_UNDERLINE, ignoreHorizontalAlignment, pixelFormat, horizontalOffset, verticalOffset, startGlyphIndex, endGlyphIndex); // Combine the two buffers // Result pixel buffer will be stored into topPixelBuffer. CombineImageBuffer(underlineImageBuffer, topPixelBuffer, bufferWidth, bufferHeight, false); @@ -1632,12 +499,15 @@ Devel::PixelBuffer Typesetter::ApplyUnderlineMarkupImageBuffer(Devel::PixelBuffe 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) { + // Use l-value to make ensure it is not nullptr, so compiler happy. + auto& viewModel = *(mImpl->GetViewModel()); + // strikethrough-tags (this is for Markup case) // Get the strikethrough runs. - const Length numberOfStrikethroughRuns = mModel->GetNumberOfStrikethroughRuns(); + const Length numberOfStrikethroughRuns = viewModel.GetNumberOfStrikethroughRuns(); Vector strikethroughRuns; strikethroughRuns.Resize(numberOfStrikethroughRuns); - mModel->GetStrikethroughRuns(strikethroughRuns.Begin(), 0u, numberOfStrikethroughRuns); + viewModel.GetStrikethroughRuns(strikethroughRuns.Begin(), 0u, numberOfStrikethroughRuns); // Iterate on the consecutive strikethrough glyph run and connect them into one chunk of strikethrough characters. Vector::ConstIterator itGlyphRun = strikethroughRuns.Begin(); @@ -1651,7 +521,7 @@ Devel::PixelBuffer Typesetter::ApplyStrikethroughMarkupImageBuffer(Devel::PixelB endGlyphIndex = startGlyphIndex + itGlyphRun->glyphRun.numberOfGlyphs - 1; // Create the image buffer for strikethrough - Devel::PixelBuffer strikethroughImageBuffer = CreateImageBuffer(bufferWidth, bufferHeight, Typesetter::STYLE_STRIKETHROUGH, ignoreHorizontalAlignment, pixelFormat, horizontalOffset, verticalOffset, startGlyphIndex, endGlyphIndex); + Devel::PixelBuffer strikethroughImageBuffer = mImpl->CreateImageBuffer(bufferWidth, bufferHeight, Typesetter::STYLE_STRIKETHROUGH, ignoreHorizontalAlignment, pixelFormat, horizontalOffset, verticalOffset, startGlyphIndex, endGlyphIndex); // Combine the two buffers // Result pixel buffer will be stored into topPixelBuffer. CombineImageBuffer(strikethroughImageBuffer, topPixelBuffer, bufferWidth, bufferHeight, false); @@ -1669,7 +539,7 @@ void Typesetter::SetMaskForImageBuffer(Devel::PixelBuffer& __restrict__ topPixel uint32_t* topBuffer = reinterpret_cast(topPixelBuffer.GetBuffer()); uint32_t* bottomBuffer = reinterpret_cast(bottomPixelBuffer.GetBuffer()); - if(topBuffer == NULL || bottomBuffer == NULL) + if(topBuffer == nullptr || bottomBuffer == nullptr) { // Nothing to do if one of both buffers are empty. return; @@ -1679,15 +549,15 @@ void Typesetter::SetMaskForImageBuffer(Devel::PixelBuffer& __restrict__ topPixel for(uint32_t pixelIndex = 0; pixelIndex < bufferSizeInt; ++pixelIndex) { - uint32_t topBufferColor = *(topBuffer); - uint32_t bottomBufferColor = *(bottomBuffer); - uint8_t* __restrict__ topBufferColorBuffer = reinterpret_cast(&topBufferColor); + uint32_t topBufferColor = *(topBuffer); + uint32_t bottomBufferColor = *(bottomBuffer); + uint8_t* __restrict__ topBufferColorBuffer = reinterpret_cast(&topBufferColor); uint8_t* __restrict__ bottomBufferColorBuffer = reinterpret_cast(&bottomBufferColor); // Return the transparency of the text to original. uint8_t originAlphaInt = originAlpha * 255; - uint8_t topAlpha = topBufferColorBuffer[3]; + uint8_t topAlpha = topBufferColorBuffer[3]; uint8_t bottomAlpha = 255 - topAlpha; // Manual blending. @@ -1705,17 +575,11 @@ void Typesetter::SetMaskForImageBuffer(Devel::PixelBuffer& __restrict__ topPixel } Typesetter::Typesetter(const ModelInterface* const model) -: mModel(new ViewModel(model)), - mFontClient() +: mImpl{std::make_unique(model)} { - // Default font client set. - mFontClient = TextAbstraction::FontClient::Get(); } -Typesetter::~Typesetter() -{ - delete mModel; -} +Typesetter::~Typesetter() = default; } // namespace Text diff --git a/dali-toolkit/internal/text/rendering/text-typesetter.h b/dali-toolkit/internal/text/rendering/text-typesetter.h index 88d73f59b1..bdbd598d4f 100644 --- a/dali-toolkit/internal/text/rendering/text-typesetter.h +++ b/dali-toolkit/internal/text/rendering/text-typesetter.h @@ -2,7 +2,7 @@ #define DALI_TOOLKIT_TEXT_TYPESETTER_H /* - * Copyright (c) 2022 Samsung Electronics Co., Ltd. + * Copyright (c) 2024 Samsung Electronics Co., Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,12 +21,13 @@ // EXTERNAL INCLUDES #include #include -#include #include +#include #include #include #include #include +#include ///< for std::unique_ptr namespace Dali { @@ -189,28 +190,6 @@ private: // Declared private and left undefined to avoid copies. Typesetter& operator=(const Typesetter& handle); - /** - * @brief Create & draw the image buffer for the given range of the glyphs in the given style. - * - * Does the following operations: - * - Retrieves the data buffers from the text model. - * - Creates the pixel data used to generate the final image with the given size. - * - Traverse the visible glyphs, retrieve their bitmaps and compose the final pixel data. - * - * @param[in] bufferWidth The width of the image buffer. - * @param[in] bufferHeight The height of the image buffer. - * @param[in] style The style of the text. - * @param[in] ignoreHorizontalAlignment Whether to ignore the horizontal alignment, not ignored by default. - * @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). - * @param[in] horizontalOffset The horizontal offset to be added to the glyph's position. - * @param[in] verticalOffset The vertical offset to be added to the glyph's position. - * @param[in] fromGlyphIndex The index of the first glyph within the text to be drawn - * @param[in] toGlyphIndex The index of the last glyph within the text to be drawn - * - * @return An image buffer with the text. - */ - Devel::PixelBuffer 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 TextAbstraction::GlyphIndex fromGlyphIndex, const TextAbstraction::GlyphIndex toGlyphIndex); - /** * @brief Apply markup underline tags. * @@ -262,8 +241,8 @@ protected: virtual ~Typesetter(); private: - ViewModel* mModel; - TextAbstraction::FontClient mFontClient; + struct Impl; + std::unique_ptr mImpl; }; } // namespace Text