Added support for outlined text

This commit is contained in:
Zachariah Brown 2015-12-29 08:14:43 -05:00 committed by Lukas Dürrenberger
parent 7ff9478061
commit 957cabb816
6 changed files with 347 additions and 106 deletions

View File

@ -71,7 +71,7 @@ int main()
pauseMessage.setFont(font); pauseMessage.setFont(font);
pauseMessage.setCharacterSize(40); pauseMessage.setCharacterSize(40);
pauseMessage.setPosition(170.f, 150.f); pauseMessage.setPosition(170.f, 150.f);
pauseMessage.setColor(sf::Color::White); pauseMessage.setFillColor(sf::Color::White);
pauseMessage.setString("Welcome to SFML pong!\nPress space to start the game"); pauseMessage.setString("Welcome to SFML pong!\nPress space to start the game");
// Define the paddles properties // Define the paddles properties

View File

@ -300,12 +300,12 @@ int main()
// Create the description text // Create the description text
sf::Text description("Current effect: " + effects[current]->getName(), font, 20); sf::Text description("Current effect: " + effects[current]->getName(), font, 20);
description.setPosition(10, 530); description.setPosition(10, 530);
description.setColor(sf::Color(80, 80, 80)); description.setFillColor(sf::Color(80, 80, 80));
// Create the instructions text // Create the instructions text
sf::Text instructions("Press left and right arrows to change the current shader", font, 20); sf::Text instructions("Press left and right arrows to change the current shader", font, 20);
instructions.setPosition(280, 555); instructions.setPosition(280, 555);
instructions.setColor(sf::Color(80, 80, 80)); instructions.setFillColor(sf::Color(80, 80, 80));
// Start the game loop // Start the game loop
sf::Clock clock; sf::Clock clock;

View File

@ -166,14 +166,18 @@ public:
/// might be available. If the glyph is not available at the /// might be available. If the glyph is not available at the
/// requested size, an empty glyph is returned. /// requested size, an empty glyph is returned.
/// ///
/// \param codePoint Unicode code point of the character to get /// Be aware that using a negative value for the outline
/// \param characterSize Reference character size /// thickness will cause distorted rendering.
/// \param bold Retrieve the bold version or the regular one? ///
/// \param codePoint Unicode code point of the character to get
/// \param characterSize Reference character size
/// \param bold Retrieve the bold version or the regular one?
/// \param outlineThickness Thickness of outline (when != 0 the glyph will not be filled)
/// ///
/// \return The glyph corresponding to \a codePoint and \a characterSize /// \return The glyph corresponding to \a codePoint and \a characterSize
/// ///
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
const Glyph& getGlyph(Uint32 codePoint, unsigned int characterSize, bool bold) const; const Glyph& getGlyph(Uint32 codePoint, unsigned int characterSize, bool bold, float outlineThickness = 0) const;
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
/// \brief Get the kerning offset of two glyphs /// \brief Get the kerning offset of two glyphs
@ -277,7 +281,7 @@ private:
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
// Types // Types
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
typedef std::map<Uint32, Glyph> GlyphTable; ///< Table mapping a codepoint to its glyph typedef std::map<Uint64, Glyph> GlyphTable; ///< Table mapping a codepoint to its glyph
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
/// \brief Structure defining a page of glyphs /// \brief Structure defining a page of glyphs
@ -302,14 +306,15 @@ private:
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
/// \brief Load a new glyph and store it in the cache /// \brief Load a new glyph and store it in the cache
/// ///
/// \param codePoint Unicode code point of the character to load /// \param codePoint Unicode code point of the character to load
/// \param characterSize Reference character size /// \param characterSize Reference character size
/// \param bold Retrieve the bold version or the regular one? /// \param bold Retrieve the bold version or the regular one?
/// \param outlineThickness Thickness of outline (when != 0 the glyph will not be filled)
/// ///
/// \return The glyph corresponding to \a codePoint and \a characterSize /// \return The glyph corresponding to \a codePoint and \a characterSize
/// ///
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
Glyph loadGlyph(Uint32 codePoint, unsigned int characterSize, bool bold) const; Glyph loadGlyph(Uint32 codePoint, unsigned int characterSize, bool bold, float outlineThickness) const;
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
/// \brief Find a suitable rectangle within the texture for a glyph /// \brief Find a suitable rectangle within the texture for a glyph
@ -344,6 +349,7 @@ private:
void* m_library; ///< Pointer to the internal library interface (it is typeless to avoid exposing implementation details) void* m_library; ///< Pointer to the internal library interface (it is typeless to avoid exposing implementation details)
void* m_face; ///< Pointer to the internal font face (it is typeless to avoid exposing implementation details) void* m_face; ///< Pointer to the internal font face (it is typeless to avoid exposing implementation details)
void* m_streamRec; ///< Pointer to the stream rec instance (it is typeless to avoid exposing implementation details) void* m_streamRec; ///< Pointer to the stream rec instance (it is typeless to avoid exposing implementation details)
void* m_stroker; ///< Pointer to the stroker (it is typeless to avoid exposing implementation details)
int* m_refCount; ///< Reference counter used by implicit sharing int* m_refCount; ///< Reference counter used by implicit sharing
Info m_info; ///< Information about the font Info m_info; ///< Information about the font
mutable PageTable m_pages; ///< Table containing the glyphs pages by character size mutable PageTable m_pages; ///< Table containing the glyphs pages by character size

View File

@ -159,16 +159,63 @@ public:
void setStyle(Uint32 style); void setStyle(Uint32 style);
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
/// \brief Set the global color of the text /// \brief Set the fill color of the text
/// ///
/// By default, the text's color is opaque white. /// By default, the text's fill color is opaque white.
/// Setting the fill color to a transparent color with an outline
/// will cause the outline to be displayed in the fill area of the text.
/// ///
/// \param color New color of the text /// \param color New fill color of the text
/// ///
/// \see getColor /// \see getFillColor
///
/// \deprecated There is now fill and outline colors instead
/// of a single global color.
/// Use setFillColor() or setOutlineColor() instead.
/// ///
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
void setColor(const Color& color); SFML_DEPRECATED void setColor(const Color& color);
////////////////////////////////////////////////////////////
/// \brief Set the fill color of the text
///
/// By default, the text's fill color is opaque white.
/// Setting the fill color to a transparent color with an outline
/// will cause the outline to be displayed in the fill area of the text.
///
/// \param color New fill color of the text
///
/// \see getFillColor
///
////////////////////////////////////////////////////////////
void setFillColor(const Color& color);
////////////////////////////////////////////////////////////
/// \brief Set the outline color of the text
///
/// By default, the text's outline color is opaque black.
///
/// \param color New outline color of the text
///
/// \see getOutlineColor
///
////////////////////////////////////////////////////////////
void setOutlineColor(const Color& color);
////////////////////////////////////////////////////////////
/// \brief Set the thickness of the text's outline
///
/// By default, the outline thickness is 0.
///
/// Be aware that using a negative value for the outline
/// thickness will cause distorted rendering.
///
/// \param thickness New outline thickness, in pixels
///
/// \see getOutlineThickness
///
////////////////////////////////////////////////////////////
void setOutlineThickness(float thickness);
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
/// \brief Get the text's string /// \brief Get the text's string
@ -224,14 +271,48 @@ public:
Uint32 getStyle() const; Uint32 getStyle() const;
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
/// \brief Get the global color of the text /// \brief Get the fill color of the text
/// ///
/// \return Global color of the text /// \return Fill color of the text
/// ///
/// \see setColor /// \see setFillColor
///
/// \deprecated There is now fill and outline colors instead
/// of a single global color.
/// Use getFillColor() or getOutlineColor() instead.
/// ///
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
const Color& getColor() const; SFML_DEPRECATED const Color& getColor() const;
////////////////////////////////////////////////////////////
/// \brief Get the fill color of the text
///
/// \return Fill color of the text
///
/// \see setFillColor
///
////////////////////////////////////////////////////////////
const Color& getFillColor() const;
////////////////////////////////////////////////////////////
/// \brief Get the outline color of the text
///
/// \return Outline color of the text
///
/// \see setOutlineColor
///
////////////////////////////////////////////////////////////
const Color& getOutlineColor() const;
////////////////////////////////////////////////////////////
/// \brief Get the outline thickness of the text
///
/// \return Outline thickness of the text, in pixels
///
/// \see setOutlineThickness
///
////////////////////////////////////////////////////////////
float getOutlineThickness() const;
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
/// \brief Return the position of the \a index-th character /// \brief Return the position of the \a index-th character
@ -305,8 +386,11 @@ private:
const Font* m_font; ///< Font used to display the string const Font* m_font; ///< Font used to display the string
unsigned int m_characterSize; ///< Base size of characters, in pixels unsigned int m_characterSize; ///< Base size of characters, in pixels
Uint32 m_style; ///< Text style (see Style enum) Uint32 m_style; ///< Text style (see Style enum)
Color m_color; ///< Text color Color m_fillColor; ///< Text fill color
mutable VertexArray m_vertices; ///< Vertex array containing the text's geometry Color m_outlineColor; ///< Text outline color
float m_outlineThickness; ///< Thickness of the text's outline
mutable VertexArray m_vertices; ///< Vertex array containing the fill geometry
mutable VertexArray m_outlineVertices; ///< Vertex array containing the outline geometry
mutable FloatRect m_bounds; ///< Bounding rectangle of the text (in local coordinates) mutable FloatRect m_bounds; ///< Bounding rectangle of the text (in local coordinates)
mutable bool m_geometryNeedUpdate; ///< Does the geometry need to be recomputed? mutable bool m_geometryNeedUpdate; ///< Does the geometry need to be recomputed?
}; };

View File

@ -37,6 +37,7 @@
#include FT_GLYPH_H #include FT_GLYPH_H
#include FT_OUTLINE_H #include FT_OUTLINE_H
#include FT_BITMAP_H #include FT_BITMAP_H
#include FT_STROKER_H
#include <cstdlib> #include <cstdlib>
#include <cstring> #include <cstring>
@ -143,6 +144,15 @@ bool Font::loadFromFile(const std::string& filename)
return false; return false;
} }
// Load the stroker that will be used to outline the font
FT_Stroker stroker;
if (FT_Stroker_New(static_cast<FT_Library>(m_library), &stroker) != 0)
{
err() << "Failed to load font \"" << filename << "\" (failed to create the stroker)" << std::endl;
return false;
}
m_stroker = stroker;
// Select the unicode character map // Select the unicode character map
if (FT_Select_Charmap(face, FT_ENCODING_UNICODE) != 0) if (FT_Select_Charmap(face, FT_ENCODING_UNICODE) != 0)
{ {
@ -197,6 +207,15 @@ bool Font::loadFromMemory(const void* data, std::size_t sizeInBytes)
return false; return false;
} }
// Load the stroker that will be used to outline the font
FT_Stroker stroker;
if (FT_Stroker_New(static_cast<FT_Library>(m_library), &stroker) != 0)
{
err() << "Failed to load font from memory (failed to create the stroker)" << std::endl;
return false;
}
m_stroker = stroker;
// Select the Unicode character map // Select the Unicode character map
if (FT_Select_Charmap(face, FT_ENCODING_UNICODE) != 0) if (FT_Select_Charmap(face, FT_ENCODING_UNICODE) != 0)
{ {
@ -261,6 +280,15 @@ bool Font::loadFromStream(InputStream& stream)
return false; return false;
} }
// Load the stroker that will be used to outline the font
FT_Stroker stroker;
if (FT_Stroker_New(static_cast<FT_Library>(m_library), &stroker) != 0)
{
err() << "Failed to load font from stream (failed to create the stroker)" << std::endl;
return false;
}
m_stroker = stroker;
// Select the Unicode character map // Select the Unicode character map
if (FT_Select_Charmap(face, FT_ENCODING_UNICODE) != 0) if (FT_Select_Charmap(face, FT_ENCODING_UNICODE) != 0)
{ {
@ -289,13 +317,15 @@ const Font::Info& Font::getInfo() const
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
const Glyph& Font::getGlyph(Uint32 codePoint, unsigned int characterSize, bool bold) const const Glyph& Font::getGlyph(Uint32 codePoint, unsigned int characterSize, bool bold, float outlineThickness) const
{ {
// Get the page corresponding to the character size // Get the page corresponding to the character size
GlyphTable& glyphs = m_pages[characterSize].glyphs; GlyphTable& glyphs = m_pages[characterSize].glyphs;
// Build the key by combining the code point and the bold flag // Build the key by combining the code point, bold flag, and outline thickness
Uint32 key = ((bold ? 1 : 0) << 31) | codePoint; Uint64 key = (static_cast<Uint64>(*reinterpret_cast<Uint32*>(&outlineThickness)) << 32)
| (static_cast<Uint64>(bold ? 1 : 0) << 31)
| static_cast<Uint64>(codePoint);
// Search the glyph into the cache // Search the glyph into the cache
GlyphTable::const_iterator it = glyphs.find(key); GlyphTable::const_iterator it = glyphs.find(key);
@ -307,7 +337,7 @@ const Glyph& Font::getGlyph(Uint32 codePoint, unsigned int characterSize, bool b
else else
{ {
// Not found: we have to load it // Not found: we have to load it
Glyph glyph = loadGlyph(codePoint, characterSize, bold); Glyph glyph = loadGlyph(codePoint, characterSize, bold, outlineThickness);
return glyphs.insert(std::make_pair(key, glyph)).first->second; return glyphs.insert(std::make_pair(key, glyph)).first->second;
} }
} }
@ -442,6 +472,10 @@ void Font::cleanup()
// Delete the reference counter // Delete the reference counter
delete m_refCount; delete m_refCount;
// Destroy the stroker
if (m_stroker)
FT_Stroker_Done(static_cast<FT_Stroker>(m_stroker));
// Destroy the font face // Destroy the font face
if (m_face) if (m_face)
FT_Done_Face(static_cast<FT_Face>(m_face)); FT_Done_Face(static_cast<FT_Face>(m_face));
@ -459,6 +493,7 @@ void Font::cleanup()
// Reset members // Reset members
m_library = NULL; m_library = NULL;
m_face = NULL; m_face = NULL;
m_stroker = NULL;
m_streamRec = NULL; m_streamRec = NULL;
m_refCount = NULL; m_refCount = NULL;
m_pages.clear(); m_pages.clear();
@ -467,7 +502,7 @@ void Font::cleanup()
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
Glyph Font::loadGlyph(Uint32 codePoint, unsigned int characterSize, bool bold) const Glyph Font::loadGlyph(Uint32 codePoint, unsigned int characterSize, bool bold, float outlineThickness) const
{ {
// The glyph to return // The glyph to return
Glyph glyph; Glyph glyph;
@ -482,7 +517,10 @@ Glyph Font::loadGlyph(Uint32 codePoint, unsigned int characterSize, bool bold) c
return glyph; return glyph;
// Load the glyph corresponding to the code point // Load the glyph corresponding to the code point
if (FT_Load_Char(face, codePoint, FT_LOAD_TARGET_NORMAL | FT_LOAD_FORCE_AUTOHINT) != 0) FT_Int32 flags = FT_LOAD_TARGET_NORMAL | FT_LOAD_FORCE_AUTOHINT;
if (outlineThickness != 0)
flags |= FT_LOAD_NO_BITMAP;
if (FT_Load_Char(face, codePoint, flags) != 0)
return glyph; return glyph;
// Retrieve the glyph // Retrieve the glyph
@ -490,13 +528,24 @@ Glyph Font::loadGlyph(Uint32 codePoint, unsigned int characterSize, bool bold) c
if (FT_Get_Glyph(face->glyph, &glyphDesc) != 0) if (FT_Get_Glyph(face->glyph, &glyphDesc) != 0)
return glyph; return glyph;
// Apply bold if necessary -- first technique using outline (highest quality) // Apply bold and outline (there is no fallback for outline) if necessary -- first technique using outline (highest quality)
FT_Pos weight = 1 << 6; FT_Pos weight = 1 << 6;
bool outline = (glyphDesc->format == FT_GLYPH_FORMAT_OUTLINE); bool outline = (glyphDesc->format == FT_GLYPH_FORMAT_OUTLINE);
if (bold && outline) if (outline)
{ {
FT_OutlineGlyph outlineGlyph = (FT_OutlineGlyph)glyphDesc; if (bold)
FT_Outline_Embolden(&outlineGlyph->outline, weight); {
FT_OutlineGlyph outlineGlyph = (FT_OutlineGlyph)glyphDesc;
FT_Outline_Embolden(&outlineGlyph->outline, weight);
}
if (outlineThickness != 0)
{
FT_Stroker stroker = static_cast<FT_Stroker>(m_stroker);
FT_Stroker_Set(stroker, static_cast<FT_Fixed>(outlineThickness * static_cast<float>(1 << 6)), FT_STROKER_LINECAP_ROUND, FT_STROKER_LINEJOIN_ROUND, 0);
FT_Glyph_Stroke(&glyphDesc, stroker, false);
}
} }
// Convert the glyph to a bitmap (i.e. rasterize it) // Convert the glyph to a bitmap (i.e. rasterize it)
@ -504,9 +553,13 @@ Glyph Font::loadGlyph(Uint32 codePoint, unsigned int characterSize, bool bold) c
FT_Bitmap& bitmap = reinterpret_cast<FT_BitmapGlyph>(glyphDesc)->bitmap; FT_Bitmap& bitmap = reinterpret_cast<FT_BitmapGlyph>(glyphDesc)->bitmap;
// Apply bold if necessary -- fallback technique using bitmap (lower quality) // Apply bold if necessary -- fallback technique using bitmap (lower quality)
if (bold && !outline) if (!outline)
{ {
FT_Bitmap_Embolden(static_cast<FT_Library>(m_library), &bitmap, weight, weight); if (bold)
FT_Bitmap_Embolden(static_cast<FT_Library>(m_library), &bitmap, weight, weight);
if (outlineThickness != 0)
err() << "Failed to outline glyph (no fallback available)" << std::endl;
} }
// Compute the glyph's advance offset // Compute the glyph's advance offset
@ -537,10 +590,10 @@ Glyph Font::loadGlyph(Uint32 codePoint, unsigned int characterSize, bool bold) c
glyph.textureRect.height -= 2 * padding; glyph.textureRect.height -= 2 * padding;
// Compute the glyph's bounding box // Compute the glyph's bounding box
glyph.bounds.left = static_cast<float>(face->glyph->metrics.horiBearingX) / static_cast<float>(1 << 6); glyph.bounds.left = static_cast<float>(face->glyph->metrics.horiBearingX) / static_cast<float>(1 << 6);
glyph.bounds.top = -static_cast<float>(face->glyph->metrics.horiBearingY) / static_cast<float>(1 << 6); glyph.bounds.top = -static_cast<float>(face->glyph->metrics.horiBearingY) / static_cast<float>(1 << 6);
glyph.bounds.width = static_cast<float>(face->glyph->metrics.width) / static_cast<float>(1 << 6); glyph.bounds.width = static_cast<float>(face->glyph->metrics.width) / static_cast<float>(1 << 6) + outlineThickness * 2;
glyph.bounds.height = static_cast<float>(face->glyph->metrics.height) / static_cast<float>(1 << 6); glyph.bounds.height = static_cast<float>(face->glyph->metrics.height) / static_cast<float>(1 << 6) + outlineThickness * 2;
// Extract the glyph's pixels from the bitmap // Extract the glyph's pixels from the bitmap
m_pixelBuffer.resize(width * height * 4, 255); m_pixelBuffer.resize(width * height * 4, 255);

View File

@ -31,6 +31,45 @@
#include <cmath> #include <cmath>
namespace
{
// Add an underline or strikethrough line to the vertex array
void addLine(sf::VertexArray& vertices, float lineLength, float lineTop, const sf::Color& color, float offset, float thickness, float outlineThickness = 0)
{
float top = std::floor(lineTop + offset - (thickness / 2) + 0.5f);
float bottom = top + std::floor(thickness + 0.5f);
vertices.append(sf::Vertex(sf::Vector2f(-outlineThickness, top - outlineThickness), color, sf::Vector2f(1, 1)));
vertices.append(sf::Vertex(sf::Vector2f(lineLength + outlineThickness, top - outlineThickness), color, sf::Vector2f(1, 1)));
vertices.append(sf::Vertex(sf::Vector2f(-outlineThickness, bottom + outlineThickness), color, sf::Vector2f(1, 1)));
vertices.append(sf::Vertex(sf::Vector2f(-outlineThickness, bottom + outlineThickness), color, sf::Vector2f(1, 1)));
vertices.append(sf::Vertex(sf::Vector2f(lineLength + outlineThickness, top - outlineThickness), color, sf::Vector2f(1, 1)));
vertices.append(sf::Vertex(sf::Vector2f(lineLength + outlineThickness, bottom + outlineThickness), color, sf::Vector2f(1, 1)));
}
// Add a glyph quad to the vertex array
void addGlyphQuad(sf::VertexArray& vertices, sf::Vector2f position, const sf::Color& color, const sf::Glyph& glyph, float italic, float outlineThickness = 0)
{
float left = glyph.bounds.left;
float top = glyph.bounds.top;
float right = glyph.bounds.left + glyph.bounds.width;
float bottom = glyph.bounds.top + glyph.bounds.height;
float u1 = static_cast<float>(glyph.textureRect.left);
float v1 = static_cast<float>(glyph.textureRect.top);
float u2 = static_cast<float>(glyph.textureRect.left + glyph.textureRect.width);
float v2 = static_cast<float>(glyph.textureRect.top + glyph.textureRect.height);
vertices.append(sf::Vertex(sf::Vector2f(position.x + left - italic * top - outlineThickness, position.y + top - outlineThickness), color, sf::Vector2f(u1, v1)));
vertices.append(sf::Vertex(sf::Vector2f(position.x + right - italic * top - outlineThickness, position.y + top - outlineThickness), color, sf::Vector2f(u2, v1)));
vertices.append(sf::Vertex(sf::Vector2f(position.x + left - italic * bottom - outlineThickness, position.y + bottom - outlineThickness), color, sf::Vector2f(u1, v2)));
vertices.append(sf::Vertex(sf::Vector2f(position.x + left - italic * bottom - outlineThickness, position.y + bottom - outlineThickness), color, sf::Vector2f(u1, v2)));
vertices.append(sf::Vertex(sf::Vector2f(position.x + right - italic * top - outlineThickness, position.y + top - outlineThickness), color, sf::Vector2f(u2, v1)));
vertices.append(sf::Vertex(sf::Vector2f(position.x + right - italic * bottom - outlineThickness, position.y + bottom - outlineThickness), color, sf::Vector2f(u2, v2)));
}
}
namespace sf namespace sf
{ {
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
@ -39,8 +78,11 @@ m_string (),
m_font (NULL), m_font (NULL),
m_characterSize (30), m_characterSize (30),
m_style (Regular), m_style (Regular),
m_color (255, 255, 255), m_fillColor (255, 255, 255),
m_outlineColor (0, 0, 0),
m_outlineThickness (0),
m_vertices (Triangles), m_vertices (Triangles),
m_outlineVertices (Triangles),
m_bounds (), m_bounds (),
m_geometryNeedUpdate(false) m_geometryNeedUpdate(false)
{ {
@ -54,8 +96,11 @@ m_string (string),
m_font (&font), m_font (&font),
m_characterSize (characterSize), m_characterSize (characterSize),
m_style (Regular), m_style (Regular),
m_color (255, 255, 255), m_fillColor (255, 255, 255),
m_outlineColor (0, 0, 0),
m_outlineThickness (0),
m_vertices (Triangles), m_vertices (Triangles),
m_outlineVertices (Triangles),
m_bounds (), m_bounds (),
m_geometryNeedUpdate(true) m_geometryNeedUpdate(true)
{ {
@ -110,21 +155,57 @@ void Text::setStyle(Uint32 style)
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
void Text::setColor(const Color& color) void Text::setColor(const Color& color)
{ {
if (color != m_color) setFillColor(color);
}
////////////////////////////////////////////////////////////
void Text::setFillColor(const Color& color)
{
if (color != m_fillColor)
{ {
m_color = color; m_fillColor = color;
// Change vertex colors directly, no need to update whole geometry // Change vertex colors directly, no need to update whole geometry
// (if geometry is updated anyway, we can skip this step) // (if geometry is updated anyway, we can skip this step)
if (!m_geometryNeedUpdate) if (!m_geometryNeedUpdate)
{ {
for (std::size_t i = 0; i < m_vertices.getVertexCount(); ++i) for (std::size_t i = 0; i < m_vertices.getVertexCount(); ++i)
m_vertices[i].color = m_color; m_vertices[i].color = m_fillColor;
} }
} }
} }
////////////////////////////////////////////////////////////
void Text::setOutlineColor(const Color& color)
{
if (color != m_outlineColor)
{
m_outlineColor = color;
// Change vertex colors directly, no need to update whole geometry
// (if geometry is updated anyway, we can skip this step)
if (!m_geometryNeedUpdate)
{
for (std::size_t i = 0; i < m_outlineVertices.getVertexCount(); ++i)
m_outlineVertices[i].color = m_outlineColor;
}
}
}
////////////////////////////////////////////////////////////
void Text::setOutlineThickness(float thickness)
{
if (thickness != m_outlineThickness)
{
m_outlineThickness = thickness;
m_geometryNeedUpdate = true;
}
}
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
const String& Text::getString() const const String& Text::getString() const
{ {
@ -156,7 +237,28 @@ Uint32 Text::getStyle() const
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
const Color& Text::getColor() const const Color& Text::getColor() const
{ {
return m_color; return getFillColor();
}
////////////////////////////////////////////////////////////
const Color& Text::getFillColor() const
{
return m_fillColor;
}
////////////////////////////////////////////////////////////
const Color& Text::getOutlineColor() const
{
return m_outlineColor;
}
////////////////////////////////////////////////////////////
float Text::getOutlineThickness() const
{
return m_outlineThickness;
} }
@ -231,6 +333,11 @@ void Text::draw(RenderTarget& target, RenderStates states) const
states.transform *= getTransform(); states.transform *= getTransform();
states.texture = &m_font->getTexture(m_characterSize); states.texture = &m_font->getTexture(m_characterSize);
// Only draw the outline if there is something to draw
if (m_outlineThickness != 0)
target.draw(m_outlineVertices, states);
target.draw(m_vertices, states); target.draw(m_vertices, states);
} }
} }
@ -248,14 +355,11 @@ void Text::ensureGeometryUpdate() const
// Clear the previous geometry // Clear the previous geometry
m_vertices.clear(); m_vertices.clear();
m_outlineVertices.clear();
m_bounds = FloatRect(); m_bounds = FloatRect();
// No font: nothing to draw // No font or text: nothing to draw
if (!m_font) if (!m_font || m_string.isEmpty())
return;
// No text: nothing to draw
if (m_string.isEmpty())
return; return;
// Compute values related to the text style // Compute values related to the text style
@ -289,35 +393,25 @@ void Text::ensureGeometryUpdate() const
Uint32 curChar = m_string[i]; Uint32 curChar = m_string[i];
// Apply the kerning offset // Apply the kerning offset
x += static_cast<float>(m_font->getKerning(prevChar, curChar, m_characterSize)); x += m_font->getKerning(prevChar, curChar, m_characterSize);
prevChar = curChar; prevChar = curChar;
// If we're using the underlined style and there's a new line, draw a line // If we're using the underlined style and there's a new line, draw a line
if (underlined && (curChar == L'\n')) if (underlined && (curChar == L'\n'))
{ {
float top = std::floor(y + underlineOffset - (underlineThickness / 2) + 0.5f); addLine(m_vertices, x, y, m_fillColor, underlineOffset, underlineThickness);
float bottom = top + std::floor(underlineThickness + 0.5f);
m_vertices.append(Vertex(Vector2f(0, top), m_color, Vector2f(1, 1))); if (m_outlineThickness != 0)
m_vertices.append(Vertex(Vector2f(x, top), m_color, Vector2f(1, 1))); addLine(m_outlineVertices, x, y, m_outlineColor, underlineOffset, underlineThickness, m_outlineThickness);
m_vertices.append(Vertex(Vector2f(0, bottom), m_color, Vector2f(1, 1)));
m_vertices.append(Vertex(Vector2f(0, bottom), m_color, Vector2f(1, 1)));
m_vertices.append(Vertex(Vector2f(x, top), m_color, Vector2f(1, 1)));
m_vertices.append(Vertex(Vector2f(x, bottom), m_color, Vector2f(1, 1)));
} }
// If we're using the strike through style and there's a new line, draw a line across all characters // If we're using the strike through style and there's a new line, draw a line across all characters
if (strikeThrough && (curChar == L'\n')) if (strikeThrough && (curChar == L'\n'))
{ {
float top = std::floor(y + strikeThroughOffset - (underlineThickness / 2) + 0.5f); addLine(m_vertices, x, y, m_fillColor, strikeThroughOffset, underlineThickness);
float bottom = top + std::floor(underlineThickness + 0.5f);
m_vertices.append(Vertex(Vector2f(0, top), m_color, Vector2f(1, 1))); if (m_outlineThickness != 0)
m_vertices.append(Vertex(Vector2f(x, top), m_color, Vector2f(1, 1))); addLine(m_outlineVertices, x, y, m_outlineColor, strikeThroughOffset, underlineThickness, m_outlineThickness);
m_vertices.append(Vertex(Vector2f(0, bottom), m_color, Vector2f(1, 1)));
m_vertices.append(Vertex(Vector2f(0, bottom), m_color, Vector2f(1, 1)));
m_vertices.append(Vertex(Vector2f(x, top), m_color, Vector2f(1, 1)));
m_vertices.append(Vertex(Vector2f(x, bottom), m_color, Vector2f(1, 1)));
} }
// Handle special characters // Handle special characters
@ -342,63 +436,67 @@ void Text::ensureGeometryUpdate() const
continue; continue;
} }
// Apply the outline
if (m_outlineThickness != 0)
{
const Glyph& glyph = m_font->getGlyph(curChar, m_characterSize, bold, m_outlineThickness);
float left = glyph.bounds.left;
float top = glyph.bounds.top;
float right = glyph.bounds.left + glyph.bounds.width;
float bottom = glyph.bounds.top + glyph.bounds.height;
// Add the outline glyph to the vertices
addGlyphQuad(m_outlineVertices, Vector2f(x, y), m_outlineColor, glyph, italic, m_outlineThickness);
// Update the current bounds with the outlined glyph bounds
minX = std::min(minX, x + left - italic * bottom - m_outlineThickness);
maxX = std::max(maxX, x + right - italic * top - m_outlineThickness);
minY = std::min(minY, y + top - m_outlineThickness);
maxY = std::max(maxY, y + bottom - m_outlineThickness);
}
// Extract the current glyph's description // Extract the current glyph's description
const Glyph& glyph = m_font->getGlyph(curChar, m_characterSize, bold); const Glyph& glyph = m_font->getGlyph(curChar, m_characterSize, bold);
float left = glyph.bounds.left; // Add the glyph to the vertices
float top = glyph.bounds.top; addGlyphQuad(m_vertices, Vector2f(x, y), m_fillColor, glyph, italic);
float right = glyph.bounds.left + glyph.bounds.width;
float bottom = glyph.bounds.top + glyph.bounds.height;
float u1 = static_cast<float>(glyph.textureRect.left); // Update the current bounds with the non outlined glyph bounds
float v1 = static_cast<float>(glyph.textureRect.top); if (m_outlineThickness == 0)
float u2 = static_cast<float>(glyph.textureRect.left + glyph.textureRect.width); {
float v2 = static_cast<float>(glyph.textureRect.top + glyph.textureRect.height); float left = glyph.bounds.left;
float top = glyph.bounds.top;
float right = glyph.bounds.left + glyph.bounds.width;
float bottom = glyph.bounds.top + glyph.bounds.height;
// Add a quad for the current character minX = std::min(minX, x + left - italic * bottom);
m_vertices.append(Vertex(Vector2f(x + left - italic * top, y + top), m_color, Vector2f(u1, v1))); maxX = std::max(maxX, x + right - italic * top);
m_vertices.append(Vertex(Vector2f(x + right - italic * top, y + top), m_color, Vector2f(u2, v1))); minY = std::min(minY, y + top);
m_vertices.append(Vertex(Vector2f(x + left - italic * bottom, y + bottom), m_color, Vector2f(u1, v2))); maxY = std::max(maxY, y + bottom);
m_vertices.append(Vertex(Vector2f(x + left - italic * bottom, y + bottom), m_color, Vector2f(u1, v2))); }
m_vertices.append(Vertex(Vector2f(x + right - italic * top, y + top), m_color, Vector2f(u2, v1)));
m_vertices.append(Vertex(Vector2f(x + right - italic * bottom, y + bottom), m_color, Vector2f(u2, v2)));
// Update the current bounds
minX = std::min(minX, x + left - italic * bottom);
maxX = std::max(maxX, x + right - italic * top);
minY = std::min(minY, y + top);
maxY = std::max(maxY, y + bottom);
// Advance to the next character // Advance to the next character
x += glyph.advance; x += glyph.advance;
} }
// If we're using the underlined style, add the last line // If we're using the underlined style, add the last line
if (underlined) if (underlined && (x > 0))
{ {
float top = std::floor(y + underlineOffset - (underlineThickness / 2) + 0.5f); addLine(m_vertices, x, y, m_fillColor, underlineOffset, underlineThickness);
float bottom = top + std::floor(underlineThickness + 0.5f);
m_vertices.append(Vertex(Vector2f(0, top), m_color, Vector2f(1, 1))); if (m_outlineThickness != 0)
m_vertices.append(Vertex(Vector2f(x, top), m_color, Vector2f(1, 1))); addLine(m_outlineVertices, x, y, m_outlineColor, underlineOffset, underlineThickness, m_outlineThickness);
m_vertices.append(Vertex(Vector2f(0, bottom), m_color, Vector2f(1, 1)));
m_vertices.append(Vertex(Vector2f(0, bottom), m_color, Vector2f(1, 1)));
m_vertices.append(Vertex(Vector2f(x, top), m_color, Vector2f(1, 1)));
m_vertices.append(Vertex(Vector2f(x, bottom), m_color, Vector2f(1, 1)));
} }
// If we're using the strike through style, add the last line across all characters // If we're using the strike through style, add the last line across all characters
if (strikeThrough) if (strikeThrough && (x > 0))
{ {
float top = std::floor(y + strikeThroughOffset - (underlineThickness / 2) + 0.5f); addLine(m_vertices, x, y, m_fillColor, strikeThroughOffset, underlineThickness);
float bottom = top + std::floor(underlineThickness + 0.5f);
m_vertices.append(Vertex(Vector2f(0, top), m_color, Vector2f(1, 1))); if (m_outlineThickness != 0)
m_vertices.append(Vertex(Vector2f(x, top), m_color, Vector2f(1, 1))); addLine(m_outlineVertices, x, y, m_outlineColor, strikeThroughOffset, underlineThickness, m_outlineThickness);
m_vertices.append(Vertex(Vector2f(0, bottom), m_color, Vector2f(1, 1)));
m_vertices.append(Vertex(Vector2f(0, bottom), m_color, Vector2f(1, 1)));
m_vertices.append(Vertex(Vector2f(x, top), m_color, Vector2f(1, 1)));
m_vertices.append(Vertex(Vector2f(x, bottom), m_color, Vector2f(1, 1)));
} }
// Update the bounding rectangle // Update the bounding rectangle