From 957cabb816f69a3d7a3909d24b9e773ffc9a65b5 Mon Sep 17 00:00:00 2001 From: Zachariah Brown Date: Tue, 29 Dec 2015 08:14:43 -0500 Subject: [PATCH] Added support for outlined text --- examples/pong/Pong.cpp | 2 +- examples/shader/Shader.cpp | 4 +- include/SFML/Graphics/Font.hpp | 24 ++-- include/SFML/Graphics/Text.hpp | 106 +++++++++++++-- src/SFML/Graphics/Font.cpp | 83 +++++++++--- src/SFML/Graphics/Text.cpp | 234 +++++++++++++++++++++++---------- 6 files changed, 347 insertions(+), 106 deletions(-) diff --git a/examples/pong/Pong.cpp b/examples/pong/Pong.cpp index e9edf231c..58c9fd716 100644 --- a/examples/pong/Pong.cpp +++ b/examples/pong/Pong.cpp @@ -71,7 +71,7 @@ int main() pauseMessage.setFont(font); pauseMessage.setCharacterSize(40); 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"); // Define the paddles properties diff --git a/examples/shader/Shader.cpp b/examples/shader/Shader.cpp index d2598918d..82ad61cf3 100644 --- a/examples/shader/Shader.cpp +++ b/examples/shader/Shader.cpp @@ -300,12 +300,12 @@ int main() // Create the description text sf::Text description("Current effect: " + effects[current]->getName(), font, 20); description.setPosition(10, 530); - description.setColor(sf::Color(80, 80, 80)); + description.setFillColor(sf::Color(80, 80, 80)); // Create the instructions text sf::Text instructions("Press left and right arrows to change the current shader", font, 20); instructions.setPosition(280, 555); - instructions.setColor(sf::Color(80, 80, 80)); + instructions.setFillColor(sf::Color(80, 80, 80)); // Start the game loop sf::Clock clock; diff --git a/include/SFML/Graphics/Font.hpp b/include/SFML/Graphics/Font.hpp index ac6b00eb6..409802bfe 100644 --- a/include/SFML/Graphics/Font.hpp +++ b/include/SFML/Graphics/Font.hpp @@ -166,14 +166,18 @@ public: /// might be available. If the glyph is not available at the /// requested size, an empty glyph is returned. /// - /// \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? + /// Be aware that using a negative value for the outline + /// thickness will cause distorted rendering. + /// + /// \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 /// //////////////////////////////////////////////////////////// - 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 @@ -277,7 +281,7 @@ private: //////////////////////////////////////////////////////////// // Types //////////////////////////////////////////////////////////// - typedef std::map GlyphTable; ///< Table mapping a codepoint to its glyph + typedef std::map GlyphTable; ///< Table mapping a codepoint to its glyph //////////////////////////////////////////////////////////// /// \brief Structure defining a page of glyphs @@ -302,14 +306,15 @@ private: //////////////////////////////////////////////////////////// /// \brief Load a new glyph and store it in the cache /// - /// \param codePoint Unicode code point of the character to load - /// \param characterSize Reference character size - /// \param bold Retrieve the bold version or the regular one? + /// \param codePoint Unicode code point of the character to load + /// \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 /// //////////////////////////////////////////////////////////// - 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 @@ -344,6 +349,7 @@ private: 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_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 Info m_info; ///< Information about the font mutable PageTable m_pages; ///< Table containing the glyphs pages by character size diff --git a/include/SFML/Graphics/Text.hpp b/include/SFML/Graphics/Text.hpp index c31ec611a..ad2c9180f 100644 --- a/include/SFML/Graphics/Text.hpp +++ b/include/SFML/Graphics/Text.hpp @@ -159,16 +159,63 @@ public: 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 @@ -224,14 +271,48 @@ public: 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 @@ -305,8 +386,11 @@ private: const Font* m_font; ///< Font used to display the string unsigned int m_characterSize; ///< Base size of characters, in pixels Uint32 m_style; ///< Text style (see Style enum) - Color m_color; ///< Text color - mutable VertexArray m_vertices; ///< Vertex array containing the text's geometry + Color m_fillColor; ///< Text fill color + 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 bool m_geometryNeedUpdate; ///< Does the geometry need to be recomputed? }; diff --git a/src/SFML/Graphics/Font.cpp b/src/SFML/Graphics/Font.cpp index 9940ce190..27183b69c 100644 --- a/src/SFML/Graphics/Font.cpp +++ b/src/SFML/Graphics/Font.cpp @@ -37,6 +37,7 @@ #include FT_GLYPH_H #include FT_OUTLINE_H #include FT_BITMAP_H +#include FT_STROKER_H #include #include @@ -143,6 +144,15 @@ bool Font::loadFromFile(const std::string& filename) return false; } + // Load the stroker that will be used to outline the font + FT_Stroker stroker; + if (FT_Stroker_New(static_cast(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 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; } + // Load the stroker that will be used to outline the font + FT_Stroker stroker; + if (FT_Stroker_New(static_cast(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 if (FT_Select_Charmap(face, FT_ENCODING_UNICODE) != 0) { @@ -261,6 +280,15 @@ bool Font::loadFromStream(InputStream& stream) return false; } + // Load the stroker that will be used to outline the font + FT_Stroker stroker; + if (FT_Stroker_New(static_cast(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 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 GlyphTable& glyphs = m_pages[characterSize].glyphs; - // Build the key by combining the code point and the bold flag - Uint32 key = ((bold ? 1 : 0) << 31) | codePoint; + // Build the key by combining the code point, bold flag, and outline thickness + Uint64 key = (static_cast(*reinterpret_cast(&outlineThickness)) << 32) + | (static_cast(bold ? 1 : 0) << 31) + | static_cast(codePoint); // Search the glyph into the cache GlyphTable::const_iterator it = glyphs.find(key); @@ -307,7 +337,7 @@ const Glyph& Font::getGlyph(Uint32 codePoint, unsigned int characterSize, bool b else { // 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; } } @@ -442,6 +472,10 @@ void Font::cleanup() // Delete the reference counter delete m_refCount; + // Destroy the stroker + if (m_stroker) + FT_Stroker_Done(static_cast(m_stroker)); + // Destroy the font face if (m_face) FT_Done_Face(static_cast(m_face)); @@ -459,6 +493,7 @@ void Font::cleanup() // Reset members m_library = NULL; m_face = NULL; + m_stroker = NULL; m_streamRec = NULL; m_refCount = NULL; 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 Glyph glyph; @@ -482,7 +517,10 @@ Glyph Font::loadGlyph(Uint32 codePoint, unsigned int characterSize, bool bold) c return glyph; // 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; // 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) 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; bool outline = (glyphDesc->format == FT_GLYPH_FORMAT_OUTLINE); - if (bold && outline) + if (outline) { - FT_OutlineGlyph outlineGlyph = (FT_OutlineGlyph)glyphDesc; - FT_Outline_Embolden(&outlineGlyph->outline, weight); + if (bold) + { + FT_OutlineGlyph outlineGlyph = (FT_OutlineGlyph)glyphDesc; + FT_Outline_Embolden(&outlineGlyph->outline, weight); + } + + if (outlineThickness != 0) + { + FT_Stroker stroker = static_cast(m_stroker); + + FT_Stroker_Set(stroker, static_cast(outlineThickness * static_cast(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) @@ -504,9 +553,13 @@ Glyph Font::loadGlyph(Uint32 codePoint, unsigned int characterSize, bool bold) c FT_Bitmap& bitmap = reinterpret_cast(glyphDesc)->bitmap; // Apply bold if necessary -- fallback technique using bitmap (lower quality) - if (bold && !outline) + if (!outline) { - FT_Bitmap_Embolden(static_cast(m_library), &bitmap, weight, weight); + if (bold) + FT_Bitmap_Embolden(static_cast(m_library), &bitmap, weight, weight); + + if (outlineThickness != 0) + err() << "Failed to outline glyph (no fallback available)" << std::endl; } // 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; // Compute the glyph's bounding box - glyph.bounds.left = static_cast(face->glyph->metrics.horiBearingX) / static_cast(1 << 6); + glyph.bounds.left = static_cast(face->glyph->metrics.horiBearingX) / static_cast(1 << 6); glyph.bounds.top = -static_cast(face->glyph->metrics.horiBearingY) / static_cast(1 << 6); - glyph.bounds.width = static_cast(face->glyph->metrics.width) / static_cast(1 << 6); - glyph.bounds.height = static_cast(face->glyph->metrics.height) / static_cast(1 << 6); + glyph.bounds.width = static_cast(face->glyph->metrics.width) / static_cast(1 << 6) + outlineThickness * 2; + glyph.bounds.height = static_cast(face->glyph->metrics.height) / static_cast(1 << 6) + outlineThickness * 2; // Extract the glyph's pixels from the bitmap m_pixelBuffer.resize(width * height * 4, 255); diff --git a/src/SFML/Graphics/Text.cpp b/src/SFML/Graphics/Text.cpp index 0635a0ee8..ec76c6fe6 100644 --- a/src/SFML/Graphics/Text.cpp +++ b/src/SFML/Graphics/Text.cpp @@ -31,6 +31,45 @@ #include +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(glyph.textureRect.left); + float v1 = static_cast(glyph.textureRect.top); + float u2 = static_cast(glyph.textureRect.left + glyph.textureRect.width); + float v2 = static_cast(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 { //////////////////////////////////////////////////////////// @@ -39,8 +78,11 @@ m_string (), m_font (NULL), m_characterSize (30), 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_outlineVertices (Triangles), m_bounds (), m_geometryNeedUpdate(false) { @@ -54,8 +96,11 @@ m_string (string), m_font (&font), m_characterSize (characterSize), 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_outlineVertices (Triangles), m_bounds (), m_geometryNeedUpdate(true) { @@ -110,21 +155,57 @@ void Text::setStyle(Uint32 style) //////////////////////////////////////////////////////////// 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 // (if geometry is updated anyway, we can skip this step) if (!m_geometryNeedUpdate) { 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 { @@ -156,7 +237,28 @@ Uint32 Text::getStyle() 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.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); } } @@ -248,14 +355,11 @@ void Text::ensureGeometryUpdate() const // Clear the previous geometry m_vertices.clear(); + m_outlineVertices.clear(); m_bounds = FloatRect(); - // No font: nothing to draw - if (!m_font) - return; - - // No text: nothing to draw - if (m_string.isEmpty()) + // No font or text: nothing to draw + if (!m_font || m_string.isEmpty()) return; // Compute values related to the text style @@ -289,35 +393,25 @@ void Text::ensureGeometryUpdate() const Uint32 curChar = m_string[i]; // Apply the kerning offset - x += static_cast(m_font->getKerning(prevChar, curChar, m_characterSize)); + x += m_font->getKerning(prevChar, curChar, m_characterSize); prevChar = curChar; // If we're using the underlined style and there's a new line, draw a line if (underlined && (curChar == L'\n')) { - float top = std::floor(y + underlineOffset - (underlineThickness / 2) + 0.5f); - float bottom = top + std::floor(underlineThickness + 0.5f); + addLine(m_vertices, x, y, m_fillColor, underlineOffset, underlineThickness); - m_vertices.append(Vertex(Vector2f(0, top), m_color, Vector2f(1, 1))); - m_vertices.append(Vertex(Vector2f(x, top), m_color, Vector2f(1, 1))); - 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 (m_outlineThickness != 0) + addLine(m_outlineVertices, x, y, m_outlineColor, underlineOffset, underlineThickness, m_outlineThickness); } // 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')) { - float top = std::floor(y + strikeThroughOffset - (underlineThickness / 2) + 0.5f); - float bottom = top + std::floor(underlineThickness + 0.5f); + addLine(m_vertices, x, y, m_fillColor, strikeThroughOffset, underlineThickness); - m_vertices.append(Vertex(Vector2f(0, top), m_color, Vector2f(1, 1))); - m_vertices.append(Vertex(Vector2f(x, top), m_color, Vector2f(1, 1))); - 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 (m_outlineThickness != 0) + addLine(m_outlineVertices, x, y, m_outlineColor, strikeThroughOffset, underlineThickness, m_outlineThickness); } // Handle special characters @@ -342,63 +436,67 @@ void Text::ensureGeometryUpdate() const 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 const Glyph& glyph = m_font->getGlyph(curChar, m_characterSize, bold); - 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 glyph to the vertices + addGlyphQuad(m_vertices, Vector2f(x, y), m_fillColor, glyph, italic); - float u1 = static_cast(glyph.textureRect.left); - float v1 = static_cast(glyph.textureRect.top); - float u2 = static_cast(glyph.textureRect.left + glyph.textureRect.width); - float v2 = static_cast(glyph.textureRect.top + glyph.textureRect.height); + // Update the current bounds with the non outlined glyph bounds + if (m_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; - // Add a quad for the current character - m_vertices.append(Vertex(Vector2f(x + left - italic * top, y + top), m_color, Vector2f(u1, v1))); - m_vertices.append(Vertex(Vector2f(x + right - italic * top, y + top), m_color, Vector2f(u2, v1))); - m_vertices.append(Vertex(Vector2f(x + left - italic * bottom, y + bottom), m_color, Vector2f(u1, v2))); - 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); + 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 x += glyph.advance; } // 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); - float bottom = top + std::floor(underlineThickness + 0.5f); + addLine(m_vertices, x, y, m_fillColor, underlineOffset, underlineThickness); - m_vertices.append(Vertex(Vector2f(0, top), m_color, Vector2f(1, 1))); - m_vertices.append(Vertex(Vector2f(x, top), m_color, Vector2f(1, 1))); - 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 (m_outlineThickness != 0) + addLine(m_outlineVertices, x, y, m_outlineColor, underlineOffset, underlineThickness, m_outlineThickness); } // 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); - float bottom = top + std::floor(underlineThickness + 0.5f); + addLine(m_vertices, x, y, m_fillColor, strikeThroughOffset, underlineThickness); - m_vertices.append(Vertex(Vector2f(0, top), m_color, Vector2f(1, 1))); - m_vertices.append(Vertex(Vector2f(x, top), m_color, Vector2f(1, 1))); - 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 (m_outlineThickness != 0) + addLine(m_outlineVertices, x, y, m_outlineColor, strikeThroughOffset, underlineThickness, m_outlineThickness); } // Update the bounding rectangle