mirror of
https://github.com/SFML/SFML.git
synced 2024-11-25 04:41:05 +08:00
Added support for outlined text
This commit is contained in:
parent
7ff9478061
commit
957cabb816
@ -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
|
||||||
|
@ -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;
|
||||||
|
@ -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.
|
||||||
///
|
///
|
||||||
|
/// 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 codePoint Unicode code point of the character to get
|
||||||
/// \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
|
||||||
///
|
///
|
||||||
////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////
|
||||||
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
|
||||||
@ -305,11 +309,12 @@ private:
|
|||||||
/// \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
|
||||||
|
@ -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?
|
||||||
};
|
};
|
||||||
|
@ -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,23 +528,38 @@ 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)
|
||||||
|
{
|
||||||
|
if (bold)
|
||||||
{
|
{
|
||||||
FT_OutlineGlyph outlineGlyph = (FT_OutlineGlyph)glyphDesc;
|
FT_OutlineGlyph outlineGlyph = (FT_OutlineGlyph)glyphDesc;
|
||||||
FT_Outline_Embolden(&outlineGlyph->outline, weight);
|
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)
|
||||||
FT_Glyph_To_Bitmap(&glyphDesc, FT_RENDER_MODE_NORMAL, 0, 1);
|
FT_Glyph_To_Bitmap(&glyphDesc, FT_RENDER_MODE_NORMAL, 0, 1);
|
||||||
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)
|
||||||
{
|
{
|
||||||
|
if (bold)
|
||||||
FT_Bitmap_Embolden(static_cast<FT_Library>(m_library), &bitmap, weight, weight);
|
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
|
||||||
@ -539,8 +592,8 @@ Glyph Font::loadGlyph(Uint32 codePoint, unsigned int characterSize, bool bold) c
|
|||||||
// 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);
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extract the current glyph's description
|
|
||||||
const Glyph& glyph = m_font->getGlyph(curChar, m_characterSize, bold);
|
// 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 left = glyph.bounds.left;
|
||||||
float top = glyph.bounds.top;
|
float top = glyph.bounds.top;
|
||||||
float right = glyph.bounds.left + glyph.bounds.width;
|
float right = glyph.bounds.left + glyph.bounds.width;
|
||||||
float bottom = glyph.bounds.top + glyph.bounds.height;
|
float bottom = glyph.bounds.top + glyph.bounds.height;
|
||||||
|
|
||||||
float u1 = static_cast<float>(glyph.textureRect.left);
|
// Add the outline glyph to the vertices
|
||||||
float v1 = static_cast<float>(glyph.textureRect.top);
|
addGlyphQuad(m_outlineVertices, Vector2f(x, y), m_outlineColor, glyph, italic, m_outlineThickness);
|
||||||
float u2 = static_cast<float>(glyph.textureRect.left + glyph.textureRect.width);
|
|
||||||
float v2 = static_cast<float>(glyph.textureRect.top + glyph.textureRect.height);
|
|
||||||
|
|
||||||
// Add a quad for the current character
|
// Update the current bounds with the outlined glyph bounds
|
||||||
m_vertices.append(Vertex(Vector2f(x + left - italic * top, y + top), m_color, Vector2f(u1, v1)));
|
minX = std::min(minX, x + left - italic * bottom - m_outlineThickness);
|
||||||
m_vertices.append(Vertex(Vector2f(x + right - italic * top, y + top), m_color, Vector2f(u2, v1)));
|
maxX = std::max(maxX, x + right - italic * top - m_outlineThickness);
|
||||||
m_vertices.append(Vertex(Vector2f(x + left - italic * bottom, y + bottom), m_color, Vector2f(u1, v2)));
|
minY = std::min(minY, y + top - m_outlineThickness);
|
||||||
m_vertices.append(Vertex(Vector2f(x + left - italic * bottom, y + bottom), m_color, Vector2f(u1, v2)));
|
maxY = std::max(maxY, y + bottom - m_outlineThickness);
|
||||||
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)));
|
|
||||||
|
// Extract the current glyph's description
|
||||||
|
const Glyph& glyph = m_font->getGlyph(curChar, m_characterSize, bold);
|
||||||
|
|
||||||
|
// Add the glyph to the vertices
|
||||||
|
addGlyphQuad(m_vertices, Vector2f(x, y), m_fillColor, glyph, italic);
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
|
||||||
// Update the current bounds
|
|
||||||
minX = std::min(minX, x + left - italic * bottom);
|
minX = std::min(minX, x + left - italic * bottom);
|
||||||
maxX = std::max(maxX, x + right - italic * top);
|
maxX = std::max(maxX, x + right - italic * top);
|
||||||
minY = std::min(minY, y + top);
|
minY = std::min(minY, y + top);
|
||||||
maxY = std::max(maxY, y + bottom);
|
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
|
||||||
|
Loading…
Reference in New Issue
Block a user