+PixelData Typesetter::Render(const Vector2& size, Toolkit::DevelText::TextDirection::Type textDirection, RenderBehaviour behaviour, bool ignoreHorizontalAlignment, Pixel::Format pixelFormat)
+{
+ // @todo. This initial implementation for a TextLabel has only one visible page.
+
+ // Elides the text if needed.
+ mModel->ElideGlyphs();
+
+ // Retrieves the layout size.
+ const Size& layoutSize = mModel->GetLayoutSize();
+
+ const int outlineWidth = static_cast<int>(mModel->GetOutlineWidth());
+
+ // Set the offset for the horizontal alignment according to the text direction and outline width.
+ int penX = 0;
+
+ switch(mModel->GetHorizontalAlignment())
+ {
+ case HorizontalAlignment::BEGIN:
+ {
+ // No offset to add.
+ break;
+ }
+ case HorizontalAlignment::CENTER:
+ {
+ penX += (textDirection == Toolkit::DevelText::TextDirection::LEFT_TO_RIGHT) ? -outlineWidth : outlineWidth;
+ break;
+ }
+ case HorizontalAlignment::END:
+ {
+ penX += (textDirection == Toolkit::DevelText::TextDirection::LEFT_TO_RIGHT) ? -outlineWidth * 2 : outlineWidth * 2;
+ break;
+ }
+ }
+
+ // Set the offset for the vertical alignment.
+ int penY = 0u;
+
+ switch(mModel->GetVerticalAlignment())
+ {
+ case VerticalAlignment::TOP:
+ {
+ // No offset to add.
+ break;
+ }
+ case VerticalAlignment::CENTER:
+ {
+ penY = static_cast<int>(0.5f * (size.height - layoutSize.height));
+ penY = penY < 0.f ? 0.f : penY;
+ break;
+ }
+ case VerticalAlignment::BOTTOM:
+ {
+ penY = static_cast<int>(size.height - layoutSize.height);
+ break;
+ }
+ }
+
+ // Calculate vertical line alignment
+ switch(mModel->GetVerticalLineAlignment())
+ {
+ case DevelText::VerticalLineAlignment::TOP:
+ {
+ break;
+ }
+ case DevelText::VerticalLineAlignment::MIDDLE:
+ {
+ const auto& line = *mModel->GetLines();
+ penY -= line.descender;
+ penY += static_cast<int>(line.lineSpacing * 0.5f + line.descender);
+ break;
+ }
+ case DevelText::VerticalLineAlignment::BOTTOM:
+ {
+ const auto& line = *mModel->GetLines();
+ const auto lineHeight = line.ascender + (-line.descender) + line.lineSpacing;
+ penY += static_cast<int>(lineHeight - (line.ascender - line.descender));
+ break;
+ }
+ }
+
+ // Generate the image buffers of the text for each different style first,
+ // then combine all of them together as one final image buffer. We try to
+ // do all of these in CPU only, so that once the final texture is generated,
+ // no calculation is needed in GPU during each frame.
+
+ const unsigned int bufferWidth = static_cast<unsigned int>(size.width);
+ const unsigned int bufferHeight = static_cast<unsigned int>(size.height);
+
+ const unsigned int bufferSizeInt = bufferWidth * bufferHeight;
+ const unsigned int bufferSizeChar = 4u * bufferSizeInt;
+
+ Length numberOfGlyphs = mModel->GetNumberOfGlyphs();
+
+ 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, 0u, numberOfGlyphs - 1);
+ }
+ else if(RENDER_NO_TEXT == behaviour)
+ {
+ // Generate an empty image buffer so that it can been combined with the image buffers for styles
+ imageBuffer = Devel::PixelBuffer::New(bufferWidth, bufferHeight, Pixel::RGBA8888);
+ memset(imageBuffer.GetBuffer(), 0u, bufferSizeChar);
+ }
+ else
+ {
+ // Generate the image buffer for the text with no style.
+ imageBuffer = CreateImageBuffer(bufferWidth, bufferHeight, Typesetter::STYLE_NONE, ignoreHorizontalAlignment, pixelFormat, penX, penY, 0u, numberOfGlyphs - 1);
+ }
+
+ if((RENDER_NO_STYLES != behaviour) && (RENDER_MASK != behaviour))
+ {
+ // Generate the outline if enabled
+ const uint16_t outlineWidth = mModel->GetOutlineWidth();
+ if(outlineWidth != 0u)
+ {
+ // Create the image buffer for outline
+ Devel::PixelBuffer outlineImageBuffer = CreateImageBuffer(bufferWidth, bufferHeight, Typesetter::STYLE_OUTLINE, ignoreHorizontalAlignment, pixelFormat, penX, penY, 0u, numberOfGlyphs - 1);
+
+ // Combine the two buffers
+ imageBuffer = CombineImageBuffer(imageBuffer, outlineImageBuffer, bufferWidth, bufferHeight);
+ }
+
+ // @todo. Support shadow and underline for partial text later on.
+
+ // Generate the shadow if enabled
+ const Vector2& shadowOffset = mModel->GetShadowOffset();
+ if(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, 0u, numberOfGlyphs - 1);
+
+ // Check whether it will be a soft shadow
+ const float& blurRadius = mModel->GetShadowBlurRadius();
+
+ if(blurRadius > Math::MACHINE_EPSILON_1)
+ {
+ shadowImageBuffer.ApplyGaussianBlur(blurRadius);
+ }
+
+ // Combine the two buffers
+ imageBuffer = CombineImageBuffer(imageBuffer, shadowImageBuffer, bufferWidth, bufferHeight);
+ }
+
+ // Generate the underline if enabled
+ const bool underlineEnabled = mModel->IsUnderlineEnabled();
+ if(underlineEnabled)
+ {
+ // Create the image buffer for underline
+ Devel::PixelBuffer underlineImageBuffer = CreateImageBuffer(bufferWidth, bufferHeight, Typesetter::STYLE_UNDERLINE, ignoreHorizontalAlignment, pixelFormat, penX, penY, 0u, numberOfGlyphs - 1);
+
+ // Combine the two buffers
+ imageBuffer = CombineImageBuffer(imageBuffer, underlineImageBuffer, bufferWidth, bufferHeight);
+ }
+
+ // Generate the background if enabled
+ const bool backgroundEnabled = mModel->IsBackgroundEnabled();
+ if(backgroundEnabled)
+ {
+ Devel::PixelBuffer backgroundImageBuffer = CreateImageBuffer(bufferWidth, bufferHeight, Typesetter::STYLE_BACKGROUND, ignoreHorizontalAlignment, pixelFormat, penX, penY, 0u, numberOfGlyphs - 1);
+
+ // Combine the two buffers
+ imageBuffer = CombineImageBuffer(imageBuffer, backgroundImageBuffer, bufferWidth, bufferHeight);
+ }
+ }
+
+ // Create the final PixelData for the combined image buffer
+ PixelData pixelData = Devel::PixelBuffer::Convert(imageBuffer);
+
+ return pixelData;
+}
+
+Devel::PixelBuffer Typesetter::CreateImageBuffer(const unsigned int bufferWidth, const unsigned int bufferHeight, Typesetter::Style style, bool ignoreHorizontalAlignment, Pixel::Format pixelFormat, int horizontalOffset, int verticalOffset, GlyphIndex fromGlyphIndex, GlyphIndex toGlyphIndex)
+{
+ // Retrieve lines, glyphs, positions and colors from the view model.
+ const Length modelNumberOfLines = mModel->GetNumberOfLines();
+ const LineRun* const modelLinesBuffer = mModel->GetLines();
+ const Length numberOfGlyphs = mModel->GetNumberOfGlyphs();
+ const GlyphInfo* const glyphsBuffer = mModel->GetGlyphs();
+ const Vector2* const positionBuffer = mModel->GetLayout();
+ const Vector4* const colorsBuffer = mModel->GetColors();
+ const ColorIndex* const colorIndexBuffer = mModel->GetColorIndices();
+
+ // 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 = Devel::PixelBuffer::New(bufferWidth, bufferHeight, pixelFormat);
+ glyphData.horizontalOffset = 0;
+
+ if(Pixel::RGBA8888 == pixelFormat)
+ {
+ const unsigned int bufferSizeInt = bufferWidth * bufferHeight;
+ const unsigned int bufferSizeChar = 4u * bufferSizeInt;
+ memset(glyphData.bitmapBuffer.GetBuffer(), 0u, bufferSizeChar);
+ }
+ else
+ {
+ memset(glyphData.bitmapBuffer.GetBuffer(), 0, bufferWidth * bufferHeight);
+ }
+
+ // Get a handle of the font client. Used to retrieve the bitmaps of the glyphs.
+ TextAbstraction::FontClient fontClient = TextAbstraction::FontClient::Get();
+
+ // 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<int>(line.alignmentOffset);
+ glyphData.horizontalOffset += horizontalOffset;
+
+ // Increases the vertical offset with the line's ascender.
+ glyphData.verticalOffset += static_cast<int>(line.ascender);
+
+ // Include line spacing after first line
+ if(lineIndex > 0u)
+ {
+ glyphData.verticalOffset += static_cast<int>(line.lineSpacing);
+ }
+
+ // Retrieves the glyph's outline width
+ float outlineWidth = static_cast<float>(mModel->GetOutlineWidth());
+
+ if(style == Typesetter::STYLE_OUTLINE)
+ {
+ glyphData.horizontalOffset -= outlineWidth;
+ if(lineIndex == 0u)
+ {
+ // Only need to add the vertical outline offset for the first line
+ glyphData.verticalOffset -= outlineWidth;
+ }
+ }
+ 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 Vector4& underlineColor = mModel->GetUnderlineColor();
+ const float underlineHeight = mModel->GetUnderlineHeight();
+
+ // Get the underline runs.
+ const Length numberOfUnderlineRuns = mModel->GetNumberOfUnderlineRuns();
+ Vector<GlyphRun> underlineRuns;
+ underlineRuns.Resize(numberOfUnderlineRuns);
+ mModel->GetUnderlineRuns(underlineRuns.Begin(), 0u, numberOfUnderlineRuns);
+
+ bool thereAreUnderlinedGlyphs = false;
+
+ float currentUnderlinePosition = 0.0f;
+ float currentUnderlineThickness = underlineHeight;
+ float maxUnderlineThickness = currentUnderlineThickness;
+
+ FontId lastUnderlinedFontId = 0;
+
+ float lineExtentLeft = bufferWidth;
+ float lineExtentRight = 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)
+ {
+ if(glyphIndex < fromGlyphIndex || glyphIndex > toGlyphIndex)
+ {
+ // Ignore any glyph that out of the specified range
+ continue;
+ }
+
+ // 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 the glyph's width or height is zero.
+ continue;
+ }
+
+ const bool underlineGlyph = underlineEnabled || IsGlyphUnderlined(glyphIndex, underlineRuns);
+ thereAreUnderlinedGlyphs = thereAreUnderlinedGlyphs || underlineGlyph;
+
+ // Are we still using the same fontId as previous
+ if(underlineGlyph && (glyphInfo->fontId != lastUnderlinedFontId))
+ {
+ // We need to fetch fresh font underline metrics
+ FetchFontUnderlineMetrics(fontClient, glyphInfo, currentUnderlinePosition, underlineHeight, currentUnderlineThickness, maxUnderlineThickness, lastUnderlinedFontId);
+ } // underline
+
+ // 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(position->x < lineExtentLeft)
+ {
+ lineExtentLeft = position->x;
+ }
+
+ if(position->x + glyphInfo->width > lineExtentRight)
+ {
+ lineExtentRight = position->x + glyphInfo->width;
+ }
+
+ // 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));
+ }
+
+ // 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)
+ {
+ fontClient.CreateBitmap(glyphInfo->fontId,
+ glyphInfo->index,
+ glyphInfo->isItalicRequired,
+ glyphInfo->isBoldRequired,
+ glyphData.glyphBitmap,
+ static_cast<int>(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;
+ }
+
+ // delete the glyphBitmap.buffer as it is now copied into glyphData.bitmapBuffer
+ delete[] glyphData.glyphBitmap.buffer;
+ glyphData.glyphBitmap.buffer = NULL;
+ }
+ }
+
+ // Draw the underline from the leftmost glyph to the rightmost glyph
+ if(thereAreUnderlinedGlyphs && style == Typesetter::STYLE_UNDERLINE)
+ {
+ DrawUnderline(underlineColor, bufferWidth, bufferHeight, glyphData, baseline, currentUnderlinePosition, maxUnderlineThickness, lineExtentLeft, lineExtentRight);
+ }
+
+ // 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);
+ }
+
+ // Increases the vertical offset with the line's descender.
+ glyphData.verticalOffset += static_cast<int>(-line.descender);
+ }
+
+ return glyphData.bitmapBuffer;
+}
+
+Devel::PixelBuffer Typesetter::CombineImageBuffer(Devel::PixelBuffer topPixelBuffer, Devel::PixelBuffer bottomPixelBuffer, const unsigned int bufferWidth, const unsigned int bufferHeight)
+{
+ unsigned char* topBuffer = topPixelBuffer.GetBuffer();
+ unsigned char* bottomBuffer = bottomPixelBuffer.GetBuffer();
+
+ Devel::PixelBuffer combinedPixelBuffer;
+
+ if(topBuffer == NULL && bottomBuffer == NULL)
+ {
+ // Nothing to do if both buffers are empty.
+ return combinedPixelBuffer;
+ }
+
+ if(topBuffer == NULL)
+ {
+ // Nothing to do if topBuffer is empty.
+ return bottomPixelBuffer;
+ }
+
+ if(bottomBuffer == NULL)
+ {
+ // Nothing to do if bottomBuffer is empty.
+ return topPixelBuffer;
+ }
+
+ // Always combine two RGBA images
+ const unsigned int bufferSizeInt = bufferWidth * bufferHeight;
+ const unsigned int bufferSizeChar = 4u * bufferSizeInt;
+
+ combinedPixelBuffer = Devel::PixelBuffer::New(bufferWidth, bufferHeight, Pixel::RGBA8888);
+ uint8_t* combinedBuffer = reinterpret_cast<uint8_t*>(combinedPixelBuffer.GetBuffer());
+ memset(combinedBuffer, 0u, bufferSizeChar);
+
+ for(unsigned int pixelIndex = 0; pixelIndex < bufferSizeInt; pixelIndex++)
+ {
+ // If the alpha of the pixel in either buffer is not fully opaque, blend the two pixels.
+ // Otherwise, copy pixel from topBuffer to combinedBuffer.
+
+ unsigned int alphaBuffer1 = topBuffer[pixelIndex * 4 + 3];
+
+ if(alphaBuffer1 != 255)
+ {
+ // At least one pixel is not fully opaque
+ // "Over" blend the the pixel from topBuffer with the pixel in bottomBuffer
+ combinedBuffer[pixelIndex * 4] = topBuffer[pixelIndex * 4] + (bottomBuffer[pixelIndex * 4] * (255 - topBuffer[pixelIndex * 4 + 3]) / 255);
+ combinedBuffer[pixelIndex * 4 + 1] = topBuffer[pixelIndex * 4 + 1] + (bottomBuffer[pixelIndex * 4 + 1] * (255 - topBuffer[pixelIndex * 4 + 3]) / 255);
+ combinedBuffer[pixelIndex * 4 + 2] = topBuffer[pixelIndex * 4 + 2] + (bottomBuffer[pixelIndex * 4 + 2] * (255 - topBuffer[pixelIndex * 4 + 3]) / 255);
+ combinedBuffer[pixelIndex * 4 + 3] = topBuffer[pixelIndex * 4 + 3] + (bottomBuffer[pixelIndex * 4 + 3] * (255 - topBuffer[pixelIndex * 4 + 3]) / 255);
+ }
+ else
+ {
+ // Copy the pixel from topBuffer to combinedBuffer
+ combinedBuffer[pixelIndex * 4] = topBuffer[pixelIndex * 4];
+ combinedBuffer[pixelIndex * 4 + 1] = topBuffer[pixelIndex * 4 + 1];
+ combinedBuffer[pixelIndex * 4 + 2] = topBuffer[pixelIndex * 4 + 2];
+ combinedBuffer[pixelIndex * 4 + 3] = topBuffer[pixelIndex * 4 + 3];
+ }
+ }
+
+ return combinedPixelBuffer;
+}
+
+Typesetter::Typesetter(const ModelInterface* const model)
+: mModel(new ViewModel(model))