Add text outlines!
This commit is contained in:
parent
8a8c233840
commit
79b0bd644c
|
|
@ -184,6 +184,10 @@ Nazara Engine:
|
||||||
- ⚠ Font, FontData and SimpleTextDrawer now use a proper TextStyleFlags instead of a UInt32
|
- ⚠ Font, FontData and SimpleTextDrawer now use a proper TextStyleFlags instead of a UInt32
|
||||||
- Almost all Math algorithms are now constexpr
|
- Almost all Math algorithms are now constexpr
|
||||||
- PhysWorld2D: Fixed callbacks not properly replacing each others when registering twice with the same collisionId (pair)
|
- PhysWorld2D: Fixed callbacks not properly replacing each others when registering twice with the same collisionId (pair)
|
||||||
|
- ⚠ **Font, FontData and SimpleTextDrawer now supports text outlining.**
|
||||||
|
- Fixed TextSprite not handling multiple textures well
|
||||||
|
- ⚠ TextSprite will now use multiple render layers by itself (the current one and the one right before, ex: [-1, 0] if base layer is 0) if you use text outlines.
|
||||||
|
- ⚠ SimpleTextDrawer no longer supports faux bold rendering
|
||||||
|
|
||||||
Nazara Development Kit:
|
Nazara Development Kit:
|
||||||
- Added ImageWidget (#139)
|
- Added ImageWidget (#139)
|
||||||
|
|
|
||||||
|
|
@ -123,12 +123,13 @@ namespace Ndk
|
||||||
lua.Push(instance->GetCachedGlyphCount());
|
lua.Push(instance->GetCachedGlyphCount());
|
||||||
return 1;
|
return 1;
|
||||||
|
|
||||||
case 2:
|
case 3:
|
||||||
{
|
{
|
||||||
unsigned int characterSize = lua.Check<unsigned int>(&argIndex);
|
unsigned int characterSize = lua.Check<unsigned int>(&argIndex);
|
||||||
Nz::TextStyleFlags style = lua.Check<Nz::TextStyleFlags>(&argIndex);
|
Nz::TextStyleFlags style = lua.Check<Nz::TextStyleFlags>(&argIndex);
|
||||||
|
float outlineThickness = lua.Check<float>(&argIndex);
|
||||||
|
|
||||||
lua.Push(instance->GetCachedGlyphCount(characterSize, style));
|
lua.Push(instance->GetCachedGlyphCount(characterSize, style, outlineThickness));
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -146,7 +147,7 @@ namespace Ndk
|
||||||
|
|
||||||
font.BindMethod("IsValid", &Nz::Font::IsValid);
|
font.BindMethod("IsValid", &Nz::Font::IsValid);
|
||||||
|
|
||||||
font.BindMethod("Precache", (bool(Nz::Font::*)(unsigned int, Nz::TextStyleFlags, const Nz::String&) const) &Nz::Font::Precache);
|
font.BindMethod("Precache", (bool(Nz::Font::*)(unsigned int, Nz::TextStyleFlags, float, const Nz::String&) const) &Nz::Font::Precache);
|
||||||
|
|
||||||
font.BindMethod("SetGlyphBorder", &Nz::Font::SetGlyphBorder);
|
font.BindMethod("SetGlyphBorder", &Nz::Font::SetGlyphBorder);
|
||||||
font.BindMethod("SetMinimumStepSize", &Nz::Font::SetMinimumStepSize);
|
font.BindMethod("SetMinimumStepSize", &Nz::Font::SetMinimumStepSize);
|
||||||
|
|
|
||||||
|
|
@ -29,9 +29,13 @@ int main(int argc, char* argv[])
|
||||||
viewer.SetTarget(&mainWindow);
|
viewer.SetTarget(&mainWindow);
|
||||||
viewer.SetProjectionType(Nz::ProjectionType_Orthogonal);
|
viewer.SetProjectionType(Nz::ProjectionType_Orthogonal);
|
||||||
|
|
||||||
|
Nz::SimpleTextDrawer textDrawer;
|
||||||
|
textDrawer.SetCharacterSize(72);
|
||||||
|
textDrawer.SetOutlineThickness(4.f);
|
||||||
|
textDrawer.SetText("Hello world !");
|
||||||
|
|
||||||
Nz::TextSpriteRef textSprite = Nz::TextSprite::New();
|
Nz::TextSpriteRef textSprite = Nz::TextSprite::New();
|
||||||
textSprite->Update(Nz::SimpleTextDrawer::Draw("Hello world !", 72));
|
textSprite->Update(textDrawer);
|
||||||
|
|
||||||
Ndk::EntityHandle text = world.CreateEntity();
|
Ndk::EntityHandle text = world.CreateEntity();
|
||||||
Ndk::NodeComponent& nodeComponent = text->AddComponent<Ndk::NodeComponent>();
|
Ndk::NodeComponent& nodeComponent = text->AddComponent<Ndk::NodeComponent>();
|
||||||
|
|
|
||||||
|
|
@ -59,6 +59,31 @@ namespace Nz
|
||||||
void OnAtlasLayerChange(const AbstractAtlas* atlas, AbstractImage* oldLayer, AbstractImage* newLayer);
|
void OnAtlasLayerChange(const AbstractAtlas* atlas, AbstractImage* oldLayer, AbstractImage* newLayer);
|
||||||
void UpdateData(InstanceData* instanceData) const override;
|
void UpdateData(InstanceData* instanceData) const override;
|
||||||
|
|
||||||
|
struct RenderKey
|
||||||
|
{
|
||||||
|
Texture* texture;
|
||||||
|
int renderOrder;
|
||||||
|
|
||||||
|
bool operator==(const RenderKey& rhs) const
|
||||||
|
{
|
||||||
|
return texture == rhs.texture && renderOrder == rhs.renderOrder;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator!=(const RenderKey& rhs) const
|
||||||
|
{
|
||||||
|
return !operator==(rhs);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct HashRenderKey
|
||||||
|
{
|
||||||
|
std::size_t operator()(const RenderKey& key) const
|
||||||
|
{
|
||||||
|
// Since renderOrder will be very small, this will be enough
|
||||||
|
return std::hash<Texture*>()(key.texture) + key.renderOrder;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
struct RenderIndices
|
struct RenderIndices
|
||||||
{
|
{
|
||||||
unsigned int first;
|
unsigned int first;
|
||||||
|
|
@ -74,7 +99,7 @@ namespace Nz
|
||||||
};
|
};
|
||||||
|
|
||||||
std::unordered_map<const AbstractAtlas*, AtlasSlots> m_atlases;
|
std::unordered_map<const AbstractAtlas*, AtlasSlots> m_atlases;
|
||||||
mutable std::unordered_map<Texture*, RenderIndices> m_renderInfos;
|
mutable std::unordered_map<RenderKey, RenderIndices, HashRenderKey> m_renderInfos;
|
||||||
mutable std::vector<VertexStruct_XY_Color_UV> m_localVertices;
|
mutable std::vector<VertexStruct_XY_Color_UV> m_localVertices;
|
||||||
Color m_color;
|
Color m_color;
|
||||||
Recti m_localBounds;
|
Recti m_localBounds;
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,7 @@ namespace Nz
|
||||||
Vector2f corners[4];
|
Vector2f corners[4];
|
||||||
AbstractImage* atlas;
|
AbstractImage* atlas;
|
||||||
bool flipped;
|
bool flipped;
|
||||||
|
int renderOrder;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Line
|
struct Line
|
||||||
|
|
|
||||||
|
|
@ -59,14 +59,14 @@ namespace Nz
|
||||||
bool Create(FontData* data);
|
bool Create(FontData* data);
|
||||||
void Destroy();
|
void Destroy();
|
||||||
|
|
||||||
bool ExtractGlyph(unsigned int characterSize, char32_t character, TextStyleFlags style, FontGlyph* glyph) const;
|
bool ExtractGlyph(unsigned int characterSize, char32_t character, TextStyleFlags style, float outlineThickness, FontGlyph* glyph) const;
|
||||||
|
|
||||||
const std::shared_ptr<AbstractAtlas>& GetAtlas() const;
|
const std::shared_ptr<AbstractAtlas>& GetAtlas() const;
|
||||||
std::size_t GetCachedGlyphCount(unsigned int characterSize, TextStyleFlags style) const;
|
std::size_t GetCachedGlyphCount(unsigned int characterSize, TextStyleFlags style, float outlineThickness) const;
|
||||||
std::size_t GetCachedGlyphCount() const;
|
std::size_t GetCachedGlyphCount() const;
|
||||||
String GetFamilyName() const;
|
String GetFamilyName() const;
|
||||||
int GetKerning(unsigned int characterSize, char32_t first, char32_t second) const;
|
int GetKerning(unsigned int characterSize, char32_t first, char32_t second) const;
|
||||||
const Glyph& GetGlyph(unsigned int characterSize, TextStyleFlags style, char32_t character) const;
|
const Glyph& GetGlyph(unsigned int characterSize, TextStyleFlags style, float outlineThickness, char32_t character) const;
|
||||||
unsigned int GetGlyphBorder() const;
|
unsigned int GetGlyphBorder() const;
|
||||||
unsigned int GetMinimumStepSize() const;
|
unsigned int GetMinimumStepSize() const;
|
||||||
const SizeInfo& GetSizeInfo(unsigned int characterSize) const;
|
const SizeInfo& GetSizeInfo(unsigned int characterSize) const;
|
||||||
|
|
@ -74,8 +74,8 @@ namespace Nz
|
||||||
|
|
||||||
bool IsValid() const;
|
bool IsValid() const;
|
||||||
|
|
||||||
bool Precache(unsigned int characterSize, TextStyleFlags style, char32_t character) const;
|
bool Precache(unsigned int characterSize, TextStyleFlags style, float outlineThickness, char32_t character) const;
|
||||||
bool Precache(unsigned int characterSize, TextStyleFlags style, const String& characterSet) const;
|
bool Precache(unsigned int characterSize, TextStyleFlags style, float outlineThickness, const String& characterSet) const;
|
||||||
|
|
||||||
void SetAtlas(const std::shared_ptr<AbstractAtlas>& atlas);
|
void SetAtlas(const std::shared_ptr<AbstractAtlas>& atlas);
|
||||||
void SetGlyphBorder(unsigned int borderSize);
|
void SetGlyphBorder(unsigned int borderSize);
|
||||||
|
|
@ -107,6 +107,7 @@ namespace Nz
|
||||||
bool requireFauxItalic;
|
bool requireFauxItalic;
|
||||||
bool flipped;
|
bool flipped;
|
||||||
bool valid;
|
bool valid;
|
||||||
|
float fauxOutlineThickness;
|
||||||
int advance;
|
int advance;
|
||||||
unsigned int layerIndex;
|
unsigned int layerIndex;
|
||||||
};
|
};
|
||||||
|
|
@ -131,11 +132,11 @@ namespace Nz
|
||||||
private:
|
private:
|
||||||
using GlyphMap = std::unordered_map<char32_t, Glyph>;
|
using GlyphMap = std::unordered_map<char32_t, Glyph>;
|
||||||
|
|
||||||
UInt64 ComputeKey(unsigned int characterSize, TextStyleFlags style) const;
|
UInt64 ComputeKey(unsigned int characterSize, TextStyleFlags style, float outlineThickness) const;
|
||||||
void OnAtlasCleared(const AbstractAtlas* atlas);
|
void OnAtlasCleared(const AbstractAtlas* atlas);
|
||||||
void OnAtlasLayerChange(const AbstractAtlas* atlas, AbstractImage* oldLayer, AbstractImage* newLayer);
|
void OnAtlasLayerChange(const AbstractAtlas* atlas, AbstractImage* oldLayer, AbstractImage* newLayer);
|
||||||
void OnAtlasRelease(const AbstractAtlas* atlas);
|
void OnAtlasRelease(const AbstractAtlas* atlas);
|
||||||
const Glyph& PrecacheGlyph(GlyphMap& glyphMap, unsigned int characterSize, TextStyleFlags style, char32_t character) const;
|
const Glyph& PrecacheGlyph(GlyphMap& glyphMap, unsigned int characterSize, TextStyleFlags style, float outlineThickness, char32_t character) const;
|
||||||
|
|
||||||
static bool Initialize();
|
static bool Initialize();
|
||||||
static void Uninitialize();
|
static void Uninitialize();
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ namespace Nz
|
||||||
FontData() = default;
|
FontData() = default;
|
||||||
virtual ~FontData();
|
virtual ~FontData();
|
||||||
|
|
||||||
virtual bool ExtractGlyph(unsigned int characterSize, char32_t character, TextStyleFlags style, FontGlyph* dst) = 0;
|
virtual bool ExtractGlyph(unsigned int characterSize, char32_t character, TextStyleFlags style, float outlineThickness, FontGlyph* dst) = 0;
|
||||||
|
|
||||||
virtual String GetFamilyName() const = 0;
|
virtual String GetFamilyName() const = 0;
|
||||||
virtual String GetStyleName() const = 0;
|
virtual String GetStyleName() const = 0;
|
||||||
|
|
@ -36,6 +36,7 @@ namespace Nz
|
||||||
virtual float QueryUnderlinePosition(unsigned int characterSize) const = 0;
|
virtual float QueryUnderlinePosition(unsigned int characterSize) const = 0;
|
||||||
virtual float QueryUnderlineThickness(unsigned int characterSize) const = 0;
|
virtual float QueryUnderlineThickness(unsigned int characterSize) const = 0;
|
||||||
|
|
||||||
|
virtual bool SupportsOutline(float outlineThickness) const = 0;
|
||||||
virtual bool SupportsStyle(TextStyleFlags style) const = 0;
|
virtual bool SupportsStyle(TextStyleFlags style) const = 0;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -38,12 +38,16 @@ namespace Nz
|
||||||
std::size_t GetGlyphCount() const override;
|
std::size_t GetGlyphCount() const override;
|
||||||
const Line& GetLine(std::size_t index) const override;
|
const Line& GetLine(std::size_t index) const override;
|
||||||
std::size_t GetLineCount() const override;
|
std::size_t GetLineCount() const override;
|
||||||
|
const Color& GetOutlineColor() const;
|
||||||
|
float GetOutlineThickness() const;
|
||||||
TextStyleFlags GetStyle() const;
|
TextStyleFlags GetStyle() const;
|
||||||
const String& GetText() const;
|
const String& GetText() const;
|
||||||
|
|
||||||
void SetCharacterSize(unsigned int characterSize);
|
void SetCharacterSize(unsigned int characterSize);
|
||||||
void SetColor(const Color& color);
|
void SetColor(const Color& color);
|
||||||
void SetFont(Font* font);
|
void SetFont(Font* font);
|
||||||
|
void SetOutlineColor(const Color& color);
|
||||||
|
void SetOutlineThickness(float thickness);
|
||||||
void SetStyle(TextStyleFlags style);
|
void SetStyle(TextStyleFlags style);
|
||||||
void SetText(const String& str);
|
void SetText(const String& str);
|
||||||
|
|
||||||
|
|
@ -51,7 +55,9 @@ namespace Nz
|
||||||
SimpleTextDrawer& operator=(SimpleTextDrawer&& drawer);
|
SimpleTextDrawer& operator=(SimpleTextDrawer&& drawer);
|
||||||
|
|
||||||
static SimpleTextDrawer Draw(const String& str, unsigned int characterSize, TextStyleFlags style = TextStyle_Regular, const Color& color = Color::White);
|
static SimpleTextDrawer Draw(const String& str, unsigned int characterSize, TextStyleFlags style = TextStyle_Regular, const Color& color = Color::White);
|
||||||
|
static SimpleTextDrawer Draw(const String& str, unsigned int characterSize, TextStyleFlags style, const Color& color, float outlineThickness, const Color& outlineColor);
|
||||||
static SimpleTextDrawer Draw(Font* font, const String& str, unsigned int characterSize, TextStyleFlags style = TextStyle_Regular, const Color& color = Color::White);
|
static SimpleTextDrawer Draw(Font* font, const String& str, unsigned int characterSize, TextStyleFlags style = TextStyle_Regular, const Color& color = Color::White);
|
||||||
|
static SimpleTextDrawer Draw(Font* font, const String& str, unsigned int characterSize, TextStyleFlags style, const Color& color, float outlineThickness, const Color& outlineColor);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void ClearGlyphs() const;
|
void ClearGlyphs() const;
|
||||||
|
|
@ -72,6 +78,7 @@ namespace Nz
|
||||||
mutable std::vector<Glyph> m_glyphs;
|
mutable std::vector<Glyph> m_glyphs;
|
||||||
mutable std::vector<Line> m_lines;
|
mutable std::vector<Line> m_lines;
|
||||||
Color m_color;
|
Color m_color;
|
||||||
|
Color m_outlineColor;
|
||||||
FontRef m_font;
|
FontRef m_font;
|
||||||
mutable Rectf m_workingBounds;
|
mutable Rectf m_workingBounds;
|
||||||
mutable Recti m_bounds;
|
mutable Recti m_bounds;
|
||||||
|
|
@ -81,6 +88,7 @@ namespace Nz
|
||||||
mutable Vector2ui m_drawPos;
|
mutable Vector2ui m_drawPos;
|
||||||
mutable bool m_colorUpdated;
|
mutable bool m_colorUpdated;
|
||||||
mutable bool m_glyphUpdated;
|
mutable bool m_glyphUpdated;
|
||||||
|
float m_outlineThickness;
|
||||||
unsigned int m_characterSize;
|
unsigned int m_characterSize;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -30,13 +30,13 @@ namespace Nz
|
||||||
{
|
{
|
||||||
for (auto& pair : m_renderInfos)
|
for (auto& pair : m_renderInfos)
|
||||||
{
|
{
|
||||||
Texture* overlay = pair.first;
|
const RenderKey& key = pair.first;
|
||||||
RenderIndices& indices = pair.second;
|
RenderIndices& indices = pair.second;
|
||||||
|
|
||||||
if (indices.count > 0)
|
if (indices.count > 0)
|
||||||
{
|
{
|
||||||
const VertexStruct_XYZ_Color_UV* vertices = reinterpret_cast<const VertexStruct_XYZ_Color_UV*>(instanceData.data.data());
|
const VertexStruct_XYZ_Color_UV* vertices = reinterpret_cast<const VertexStruct_XYZ_Color_UV*>(instanceData.data.data());
|
||||||
renderQueue->AddSprites(instanceData.renderOrder, GetMaterial(), &vertices[indices.first * 4], indices.count, scissorRect, overlay);
|
renderQueue->AddSprites(instanceData.renderOrder + key.renderOrder, GetMaterial(), &vertices[indices.first * 4], indices.count, scissorRect, key.texture);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -101,15 +101,16 @@ namespace Nz
|
||||||
}
|
}
|
||||||
|
|
||||||
std::size_t glyphCount = drawer.GetGlyphCount();
|
std::size_t glyphCount = drawer.GetGlyphCount();
|
||||||
m_localVertices.resize(glyphCount * 4);
|
|
||||||
|
|
||||||
// Reset glyph count for every texture to zero
|
// Reset glyph count for every texture to zero
|
||||||
for (auto& pair : m_renderInfos)
|
for (auto& pair : m_renderInfos)
|
||||||
pair.second.count = 0;
|
pair.second.count = 0;
|
||||||
|
|
||||||
// Count glyph count for each texture
|
// Count glyph count for each texture
|
||||||
Texture* lastTexture = nullptr;
|
RenderKey lastRenderKey { nullptr, 0 };
|
||||||
unsigned int* count = nullptr;
|
unsigned int* count = nullptr;
|
||||||
|
|
||||||
|
std::size_t visibleGlyphCount = 0;
|
||||||
for (std::size_t i = 0; i < glyphCount; ++i)
|
for (std::size_t i = 0; i < glyphCount; ++i)
|
||||||
{
|
{
|
||||||
const AbstractTextDrawer::Glyph& glyph = drawer.GetGlyph(i);
|
const AbstractTextDrawer::Glyph& glyph = drawer.GetGlyph(i);
|
||||||
|
|
@ -117,19 +118,23 @@ namespace Nz
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
Texture* texture = static_cast<Texture*>(glyph.atlas);
|
Texture* texture = static_cast<Texture*>(glyph.atlas);
|
||||||
if (lastTexture != texture)
|
RenderKey renderKey{ texture, glyph.renderOrder };
|
||||||
|
if (lastRenderKey != renderKey)
|
||||||
{
|
{
|
||||||
auto it = m_renderInfos.find(texture);
|
auto it = m_renderInfos.find(renderKey);
|
||||||
if (it == m_renderInfos.end())
|
if (it == m_renderInfos.end())
|
||||||
it = m_renderInfos.insert(std::make_pair(texture, RenderIndices{0U, 0U})).first;
|
it = m_renderInfos.insert(std::make_pair(renderKey, RenderIndices{0U, 0U})).first;
|
||||||
|
|
||||||
count = &it->second.count;
|
count = &it->second.count;
|
||||||
lastTexture = texture;
|
lastRenderKey = renderKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
(*count)++;
|
(*count)++;
|
||||||
|
visibleGlyphCount++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
m_localVertices.resize(visibleGlyphCount * 4);
|
||||||
|
|
||||||
// Attributes indices and reinitialize glyph count to zero to use it as a counter in the next loop
|
// Attributes indices and reinitialize glyph count to zero to use it as a counter in the next loop
|
||||||
// This is because the 1st glyph can use texture A, the 2nd glyph can use texture B and the 3th glyph C can use texture A again
|
// This is because the 1st glyph can use texture A, the 2nd glyph can use texture B and the 3th glyph C can use texture A again
|
||||||
// so we need a counter to know where to write informations
|
// so we need a counter to know where to write informations
|
||||||
|
|
@ -140,7 +145,7 @@ namespace Nz
|
||||||
{
|
{
|
||||||
RenderIndices& indices = infoIt->second;
|
RenderIndices& indices = infoIt->second;
|
||||||
if (indices.count == 0)
|
if (indices.count == 0)
|
||||||
m_renderInfos.erase(infoIt++); //< No glyph uses this texture, remove from indices
|
infoIt = m_renderInfos.erase(infoIt); //< No glyph uses this texture, remove from indices
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
indices.first = index;
|
indices.first = index;
|
||||||
|
|
@ -151,7 +156,7 @@ namespace Nz
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
lastTexture = nullptr;
|
lastRenderKey = { nullptr, 0 };
|
||||||
RenderIndices* indices = nullptr;
|
RenderIndices* indices = nullptr;
|
||||||
for (unsigned int i = 0; i < glyphCount; ++i)
|
for (unsigned int i = 0; i < glyphCount; ++i)
|
||||||
{
|
{
|
||||||
|
|
@ -160,10 +165,11 @@ namespace Nz
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
Texture* texture = static_cast<Texture*>(glyph.atlas);
|
Texture* texture = static_cast<Texture*>(glyph.atlas);
|
||||||
if (lastTexture != texture)
|
RenderKey renderKey{ texture, glyph.renderOrder };
|
||||||
|
if (lastRenderKey != renderKey)
|
||||||
{
|
{
|
||||||
indices = &m_renderInfos[texture]; //< We changed texture, adjust the pointer
|
indices = &m_renderInfos[renderKey]; //< We changed texture, adjust the pointer
|
||||||
lastTexture = texture;
|
lastRenderKey = renderKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
// First, compute the uv coordinates from our atlas rect
|
// First, compute the uv coordinates from our atlas rect
|
||||||
|
|
@ -185,9 +191,10 @@ namespace Nz
|
||||||
for (unsigned int j = 0; j < 4; ++j)
|
for (unsigned int j = 0; j < 4; ++j)
|
||||||
{
|
{
|
||||||
// Remember that indices->count is a counter here, not a count value
|
// Remember that indices->count is a counter here, not a count value
|
||||||
m_localVertices[indices->count * 4 + j].color = glyph.color;
|
std::size_t offset = (indices->first + indices->count) * 4 + j;
|
||||||
m_localVertices[indices->count * 4 + j].position.Set(glyph.corners[j]);
|
m_localVertices[offset].color = glyph.color;
|
||||||
m_localVertices[indices->count * 4 + j].uv.Set(uvRect.GetCorner((glyph.flipped) ? flippedCorners[j] : normalCorners[j]));
|
m_localVertices[offset].position.Set(glyph.corners[j]);
|
||||||
|
m_localVertices[offset].uv.Set(uvRect.GetCorner((glyph.flipped) ? flippedCorners[j] : normalCorners[j]));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Increment the counter, go to next glyph
|
// Increment the counter, go to next glyph
|
||||||
|
|
@ -236,13 +243,12 @@ namespace Nz
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* \brief Handle the invalidation of an atlas layer
|
* \brief Handle the size change of an atlas layer
|
||||||
*
|
*
|
||||||
* \param atlas Atlas being invalidated
|
* \param atlas Atlas being invalidated
|
||||||
* \param oldLayer Pointer to the previous layer
|
* \param oldLayer Pointer to the previous layer
|
||||||
* \param newLayer Pointer to the new layer
|
* \param newLayer Pointer to the new layer
|
||||||
*/
|
*/
|
||||||
|
|
||||||
void TextSprite::OnAtlasLayerChange(const AbstractAtlas* atlas, AbstractImage* oldLayer, AbstractImage* newLayer)
|
void TextSprite::OnAtlasLayerChange(const AbstractAtlas* atlas, AbstractImage* oldLayer, AbstractImage* newLayer)
|
||||||
{
|
{
|
||||||
NazaraUnused(atlas);
|
NazaraUnused(atlas);
|
||||||
|
|
@ -255,33 +261,38 @@ namespace Nz
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
if (!oldLayer)
|
||||||
|
return;
|
||||||
|
|
||||||
|
assert(newLayer);
|
||||||
|
|
||||||
// The texture of an atlas have just been recreated (size change)
|
// The texture of an atlas have just been recreated (size change)
|
||||||
// we have to adjust the coordinates of the texture and the rendering texture
|
// we have to adjust the coordinates of the texture and the rendering texture
|
||||||
Texture* oldTexture = static_cast<Texture*>(oldLayer);
|
Texture* oldTexture = static_cast<Texture*>(oldLayer);
|
||||||
Texture* newTexture = static_cast<Texture*>(newLayer);
|
Texture* newTexture = static_cast<Texture*>(newLayer);
|
||||||
|
|
||||||
// It is possible that we don't use the texture (the atlas warning us for each of its layers)
|
Vector2ui oldSize(oldTexture->GetSize());
|
||||||
auto it = m_renderInfos.find(oldTexture);
|
Vector2ui newSize(newTexture->GetSize());
|
||||||
if (it != m_renderInfos.end())
|
Vector2f scale = Vector2f(oldSize) / Vector2f(newSize); // ratio of the old one to the new one
|
||||||
|
|
||||||
|
// It is possible we actually use that texture multiple times, check them all
|
||||||
|
for (auto it = m_renderInfos.begin(); it != m_renderInfos.end(); ++it)
|
||||||
{
|
{
|
||||||
// We indeed use this texture, we have to update its coordinates
|
const RenderKey& renderKey = it->first;
|
||||||
RenderIndices indices = std::move(it->second);
|
const RenderIndices& indices = it->second;
|
||||||
|
|
||||||
Vector2ui oldSize(oldTexture->GetSize());
|
// Adjust texture coordinates by size ratio
|
||||||
Vector2ui newSize(newTexture->GetSize());
|
SparsePtr<Vector2f> texCoordPtr(&m_localVertices[indices.first].uv, sizeof(VertexStruct_XY_Color_UV));
|
||||||
Vector2f scale = Vector2f(oldSize) / Vector2f(newSize); // ratio of the old one to the new one
|
|
||||||
|
|
||||||
// Now we will iterate through each coordinates of the concerned texture to multiply them by the ratio
|
|
||||||
SparsePtr<Vector2f> texCoordPtr(&m_localVertices[indices.first].uv, sizeof(VertexStruct_XYZ_Color_UV));
|
|
||||||
for (unsigned int i = 0; i < indices.count; ++i)
|
for (unsigned int i = 0; i < indices.count; ++i)
|
||||||
{
|
{
|
||||||
for (unsigned int j = 0; j < 4; ++j)
|
for (unsigned int j = 0; j < 4; ++j)
|
||||||
m_localVertices[i*4 + j].uv *= scale;
|
m_localVertices[i * 4 + j].uv *= scale;
|
||||||
}
|
}
|
||||||
|
|
||||||
// We get rid off the old texture and we set the new one at the place (same for indices)
|
// Erase and re-insert with the new texture handle
|
||||||
m_renderInfos.erase(it);
|
m_renderInfos.erase(it);
|
||||||
m_renderInfos.insert(std::make_pair(newTexture, std::move(indices)));
|
m_renderInfos.insert(std::make_pair(RenderKey{ newTexture, renderKey.renderOrder }, indices));
|
||||||
|
it = m_renderInfos.begin(); //< std::unordered_map::insert may invalidate all iterators, start from the beginning...
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -109,7 +109,7 @@ namespace Nz
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Font::ExtractGlyph(unsigned int characterSize, char32_t character, TextStyleFlags style, FontGlyph* glyph) const
|
bool Font::ExtractGlyph(unsigned int characterSize, char32_t character, TextStyleFlags style, float outlineThickness, FontGlyph* glyph) const
|
||||||
{
|
{
|
||||||
#if NAZARA_UTILITY_SAFE
|
#if NAZARA_UTILITY_SAFE
|
||||||
if (!IsValid())
|
if (!IsValid())
|
||||||
|
|
@ -119,7 +119,7 @@ namespace Nz
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
return m_data->ExtractGlyph(characterSize, character, style, glyph);
|
return m_data->ExtractGlyph(characterSize, character, style, outlineThickness, glyph);
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::shared_ptr<AbstractAtlas>& Font::GetAtlas() const
|
const std::shared_ptr<AbstractAtlas>& Font::GetAtlas() const
|
||||||
|
|
@ -127,9 +127,9 @@ namespace Nz
|
||||||
return m_atlas;
|
return m_atlas;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::size_t Font::GetCachedGlyphCount(unsigned int characterSize, TextStyleFlags style) const
|
std::size_t Font::GetCachedGlyphCount(unsigned int characterSize, TextStyleFlags style, float outlineThickness) const
|
||||||
{
|
{
|
||||||
UInt64 key = ComputeKey(characterSize, style);
|
UInt64 key = ComputeKey(characterSize, style, outlineThickness);
|
||||||
auto it = m_glyphes.find(key);
|
auto it = m_glyphes.find(key);
|
||||||
if (it == m_glyphes.end())
|
if (it == m_glyphes.end())
|
||||||
return 0;
|
return 0;
|
||||||
|
|
@ -169,28 +169,27 @@ namespace Nz
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// On utilise un cache car la méthode interne QueryKerning peut se révéler coûteuse (car pouvant induire un changement de taille)
|
// Use a cache as QueryKerning may be costly (may induce an internal size change)
|
||||||
auto& map = m_kerningCache[characterSize];
|
auto& map = m_kerningCache[characterSize];
|
||||||
|
|
||||||
UInt64 key = (static_cast<UInt64>(first) << 32) | second; // Combinaison de deux caractères 32 bits dans un nombre 64 bits
|
UInt64 key = (static_cast<UInt64>(first) << 32) | second;
|
||||||
|
|
||||||
auto it = map.find(key);
|
auto it = map.find(key);
|
||||||
if (it == map.end())
|
if (it == map.end())
|
||||||
{
|
{
|
||||||
// Absent du cache: on va demander l'information à la police
|
|
||||||
int kerning = m_data->QueryKerning(characterSize, first, second);
|
int kerning = m_data->QueryKerning(characterSize, first, second);
|
||||||
map.insert(std::make_pair(key, kerning));
|
map.insert(std::make_pair(key, kerning));
|
||||||
|
|
||||||
return kerning;
|
return kerning;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
return it->second; // Présent dans le cache, tout va bien
|
return it->second;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Font::Glyph& Font::GetGlyph(unsigned int characterSize, TextStyleFlags style, char32_t character) const
|
const Font::Glyph& Font::GetGlyph(unsigned int characterSize, TextStyleFlags style, float outlineThickness, char32_t character) const
|
||||||
{
|
{
|
||||||
UInt64 key = ComputeKey(characterSize, style);
|
UInt64 key = ComputeKey(characterSize, style, outlineThickness);
|
||||||
return PrecacheGlyph(m_glyphes[key], characterSize, style, character);
|
return PrecacheGlyph(m_glyphes[key], characterSize, style, outlineThickness, character);
|
||||||
}
|
}
|
||||||
|
|
||||||
unsigned int Font::GetGlyphBorder() const
|
unsigned int Font::GetGlyphBorder() const
|
||||||
|
|
@ -224,11 +223,11 @@ namespace Nz
|
||||||
sizeInfo.underlineThickness = m_data->QueryUnderlineThickness(characterSize);
|
sizeInfo.underlineThickness = m_data->QueryUnderlineThickness(characterSize);
|
||||||
|
|
||||||
FontGlyph glyph;
|
FontGlyph glyph;
|
||||||
if (m_data->ExtractGlyph(characterSize, ' ', TextStyle_Regular, &glyph))
|
if (m_data->ExtractGlyph(characterSize, ' ', TextStyle_Regular, 0.f, &glyph))
|
||||||
sizeInfo.spaceAdvance = glyph.advance;
|
sizeInfo.spaceAdvance = glyph.advance;
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
NazaraWarning("Failed to extract space character from font, using half the size");
|
NazaraWarning("Failed to extract space character from font, using half the character size");
|
||||||
sizeInfo.spaceAdvance = characterSize/2;
|
sizeInfo.spaceAdvance = characterSize/2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -256,13 +255,13 @@ namespace Nz
|
||||||
return m_data != nullptr;
|
return m_data != nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Font::Precache(unsigned int characterSize, TextStyleFlags style, char32_t character) const
|
bool Font::Precache(unsigned int characterSize, TextStyleFlags style, float outlineThickness, char32_t character) const
|
||||||
{
|
{
|
||||||
UInt64 key = ComputeKey(characterSize, style);
|
UInt64 key = ComputeKey(characterSize, style, outlineThickness);
|
||||||
return PrecacheGlyph(m_glyphes[key], characterSize, style, character).valid;
|
return PrecacheGlyph(m_glyphes[key], characterSize, style, outlineThickness, character).valid;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Font::Precache(unsigned int characterSize, TextStyleFlags style, const String& characterSet) const
|
bool Font::Precache(unsigned int characterSize, TextStyleFlags style, float outlineThickness, const String& characterSet) const
|
||||||
{
|
{
|
||||||
///TODO: Itération UTF-8 => UTF-32 sans allocation de buffer (Exposer utf8cpp ?)
|
///TODO: Itération UTF-8 => UTF-32 sans allocation de buffer (Exposer utf8cpp ?)
|
||||||
std::u32string set = characterSet.GetUtf32String();
|
std::u32string set = characterSet.GetUtf32String();
|
||||||
|
|
@ -272,10 +271,10 @@ namespace Nz
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
UInt64 key = ComputeKey(characterSize, style);
|
UInt64 key = ComputeKey(characterSize, style, outlineThickness);
|
||||||
auto& glyphMap = m_glyphes[key];
|
auto& glyphMap = m_glyphes[key];
|
||||||
for (char32_t character : set)
|
for (char32_t character : set)
|
||||||
PrecacheGlyph(glyphMap, characterSize, style, character);
|
PrecacheGlyph(glyphMap, characterSize, style, outlineThickness, character);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
@ -317,13 +316,7 @@ namespace Nz
|
||||||
{
|
{
|
||||||
if (m_minimumStepSize != minimumStepSize)
|
if (m_minimumStepSize != minimumStepSize)
|
||||||
{
|
{
|
||||||
#if NAZARA_UTILITY_SAFE
|
NazaraAssert(minimumStepSize != 0, "Minimum step size cannot be zero");
|
||||||
if (minimumStepSize == 0)
|
|
||||||
{
|
|
||||||
NazaraError("Minimum step size cannot be zero as it implies division by zero");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
m_minimumStepSize = minimumStepSize;
|
m_minimumStepSize = minimumStepSize;
|
||||||
ClearGlyphCache();
|
ClearGlyphCache();
|
||||||
|
|
@ -399,21 +392,21 @@ namespace Nz
|
||||||
s_defaultMinimumStepSize = minimumStepSize;
|
s_defaultMinimumStepSize = minimumStepSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
UInt64 Font::ComputeKey(unsigned int characterSize, TextStyleFlags style) const
|
UInt64 Font::ComputeKey(unsigned int characterSize, TextStyleFlags style, float outlineThickness) const
|
||||||
{
|
{
|
||||||
// On prend le pas en compte
|
// Adjust size to step size
|
||||||
UInt64 sizePart = static_cast<UInt32>((characterSize/m_minimumStepSize)*m_minimumStepSize);
|
UInt64 sizeStylePart = static_cast<UInt32>((characterSize/m_minimumStepSize)*m_minimumStepSize);
|
||||||
|
sizeStylePart = std::min<UInt64>(sizeStylePart, Nz::IntegralPow(2, 30)); //< 2^30 should be more than enough as a max size
|
||||||
// Ainsi que le style (uniquement le gras et l'italique, les autres sont gérés par un TextDrawer)
|
sizeStylePart <<= 2;
|
||||||
TextStyleFlags stylePart = 0;
|
|
||||||
|
|
||||||
|
// Store bold and italic flags (other style are handled directly by a TextDrawer)
|
||||||
if (style & TextStyle_Bold)
|
if (style & TextStyle_Bold)
|
||||||
stylePart |= TextStyle_Bold;
|
sizeStylePart |= 1 << 0;
|
||||||
|
|
||||||
if (style & TextStyle_Italic)
|
if (style & TextStyle_Italic)
|
||||||
stylePart |= TextStyle_Italic;
|
sizeStylePart |= 1 << 1;
|
||||||
|
|
||||||
return (static_cast<Nz::UInt64>(stylePart) << 32) | sizePart;
|
return (sizeStylePart << 32) | reinterpret_cast<Nz::UInt32&>(outlineThickness);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Font::OnAtlasCleared(const AbstractAtlas* atlas)
|
void Font::OnAtlasCleared(const AbstractAtlas* atlas)
|
||||||
|
|
@ -471,13 +464,13 @@ namespace Nz
|
||||||
NazaraError("Atlas has been released while in use");
|
NazaraError("Atlas has been released while in use");
|
||||||
}
|
}
|
||||||
|
|
||||||
const Font::Glyph& Font::PrecacheGlyph(GlyphMap& glyphMap, unsigned int characterSize, TextStyleFlags style, char32_t character) const
|
const Font::Glyph& Font::PrecacheGlyph(GlyphMap& glyphMap, unsigned int characterSize, TextStyleFlags style, float outlineThickness, char32_t character) const
|
||||||
{
|
{
|
||||||
auto it = glyphMap.find(character);
|
auto it = glyphMap.find(character);
|
||||||
if (it != glyphMap.end()) // Si le glyphe n'est pas déjà chargé
|
if (it != glyphMap.end())
|
||||||
return it->second;
|
return it->second;
|
||||||
|
|
||||||
Glyph& glyph = glyphMap[character]; // Insertion du glyphe
|
Glyph& glyph = glyphMap[character]; //< Insert a new glyph
|
||||||
glyph.valid = false;
|
glyph.valid = false;
|
||||||
|
|
||||||
#if NAZARA_UTILITY_SAFE
|
#if NAZARA_UTILITY_SAFE
|
||||||
|
|
@ -488,7 +481,8 @@ namespace Nz
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// On vérifie que le style demandé est supporté par la police (dans le cas contraire il devra être simulé au rendu)
|
// Check if requested style is supported by our font (otherwise it will need to be simulated)
|
||||||
|
glyph.fauxOutlineThickness = 0.f;
|
||||||
glyph.requireFauxBold = false;
|
glyph.requireFauxBold = false;
|
||||||
glyph.requireFauxItalic = false;
|
glyph.requireFauxItalic = false;
|
||||||
|
|
||||||
|
|
@ -505,12 +499,18 @@ namespace Nz
|
||||||
supportedStyle &= ~TextStyle_Italic;
|
supportedStyle &= ~TextStyle_Italic;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Est-ce que la police supporte le style demandé ?
|
float supportedOutlineThickness = outlineThickness;
|
||||||
if (style == supportedStyle)
|
if (outlineThickness > 0.f && !m_data->SupportsOutline(outlineThickness))
|
||||||
|
{
|
||||||
|
glyph.fauxOutlineThickness = supportedOutlineThickness;
|
||||||
|
supportedOutlineThickness = 0.f;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Does font support requested style?
|
||||||
|
if (style == supportedStyle && outlineThickness == supportedOutlineThickness)
|
||||||
{
|
{
|
||||||
// On extrait le glyphe depuis la police
|
|
||||||
FontGlyph fontGlyph;
|
FontGlyph fontGlyph;
|
||||||
if (ExtractGlyph(characterSize, character, style, &fontGlyph))
|
if (ExtractGlyph(characterSize, character, style, outlineThickness, &fontGlyph))
|
||||||
{
|
{
|
||||||
if (fontGlyph.image.IsValid())
|
if (fontGlyph.image.IsValid())
|
||||||
{
|
{
|
||||||
|
|
@ -523,21 +523,20 @@ namespace Nz
|
||||||
glyph.atlasRect.height = 0;
|
glyph.atlasRect.height = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Insertion du rectangle dans l'un des atlas
|
// Insert rectangle (if not empty) into our atlas
|
||||||
if (glyph.atlasRect.width > 0 && glyph.atlasRect.height > 0) // Si l'image contient quelque chose
|
if (glyph.atlasRect.width > 0 && glyph.atlasRect.height > 0)
|
||||||
{
|
{
|
||||||
// Bordure (pour éviter le débordement lors du filtrage)
|
// Add a small border to prevent GPU to sample another glyph pixel
|
||||||
glyph.atlasRect.width += m_glyphBorder*2;
|
glyph.atlasRect.width += m_glyphBorder*2;
|
||||||
glyph.atlasRect.height += m_glyphBorder*2;
|
glyph.atlasRect.height += m_glyphBorder*2;
|
||||||
|
|
||||||
// Insertion du rectangle dans l'atlas virtuel
|
|
||||||
if (!m_atlas->Insert(fontGlyph.image, &glyph.atlasRect, &glyph.flipped, &glyph.layerIndex))
|
if (!m_atlas->Insert(fontGlyph.image, &glyph.atlasRect, &glyph.flipped, &glyph.layerIndex))
|
||||||
{
|
{
|
||||||
NazaraError("Failed to insert glyph into atlas");
|
NazaraError("Failed to insert glyph into atlas");
|
||||||
return glyph;
|
return glyph;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compensation de la bordure (centrage du glyphe)
|
// Recenter and remove glyph border
|
||||||
glyph.atlasRect.x += m_glyphBorder;
|
glyph.atlasRect.x += m_glyphBorder;
|
||||||
glyph.atlasRect.y += m_glyphBorder;
|
glyph.atlasRect.y += m_glyphBorder;
|
||||||
glyph.atlasRect.width -= m_glyphBorder*2;
|
glyph.atlasRect.width -= m_glyphBorder*2;
|
||||||
|
|
@ -549,16 +548,13 @@ namespace Nz
|
||||||
glyph.valid = true;
|
glyph.valid = true;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
|
||||||
NazaraWarning("Failed to extract glyph \"" + String::Unicode(character) + "\"");
|
NazaraWarning("Failed to extract glyph \"" + String::Unicode(character) + "\"");
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// La police ne supporte pas le style demandé, nous allons donc précharger le glyphe supportant le style "minimum" supporté
|
// Font doesn't support request style, precache the minimal supported version and copy its data
|
||||||
// et copier ses données
|
UInt64 newKey = ComputeKey(characterSize, supportedStyle, supportedOutlineThickness);
|
||||||
UInt64 newKey = ComputeKey(characterSize, supportedStyle);
|
const Glyph& referenceGlyph = PrecacheGlyph(m_glyphes[newKey], characterSize, supportedStyle, supportedOutlineThickness, character);
|
||||||
const Glyph& referenceGlyph = PrecacheGlyph(m_glyphes[newKey], characterSize, supportedStyle, character);
|
|
||||||
if (referenceGlyph.valid)
|
if (referenceGlyph.valid)
|
||||||
{
|
{
|
||||||
glyph.aabb = referenceGlyph.aabb;
|
glyph.aabb = referenceGlyph.aabb;
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,9 @@
|
||||||
#include <freetype/ft2build.h>
|
#include <freetype/ft2build.h>
|
||||||
#include FT_FREETYPE_H
|
#include FT_FREETYPE_H
|
||||||
#include FT_BITMAP_H
|
#include FT_BITMAP_H
|
||||||
|
#include FT_STROKER_H
|
||||||
#include FT_OUTLINE_H
|
#include FT_OUTLINE_H
|
||||||
|
#include <Nazara/Core/CallOnExit.hpp>
|
||||||
#include <Nazara/Core/Error.hpp>
|
#include <Nazara/Core/Error.hpp>
|
||||||
#include <Nazara/Core/MemoryView.hpp>
|
#include <Nazara/Core/MemoryView.hpp>
|
||||||
#include <Nazara/Core/Stream.hpp>
|
#include <Nazara/Core/Stream.hpp>
|
||||||
|
|
@ -24,8 +26,10 @@ namespace Nz
|
||||||
class FreeTypeLibrary;
|
class FreeTypeLibrary;
|
||||||
|
|
||||||
FT_Library s_library;
|
FT_Library s_library;
|
||||||
|
FT_Stroker s_stroker;
|
||||||
std::shared_ptr<FreeTypeLibrary> s_libraryOwner;
|
std::shared_ptr<FreeTypeLibrary> s_libraryOwner;
|
||||||
constexpr float s_invScaleFactor = 1.f / (1 << 6); // 1/64
|
constexpr float s_scaleFactor = 1 << 6;
|
||||||
|
constexpr float s_invScaleFactor = 1.f / s_scaleFactor;
|
||||||
|
|
||||||
extern "C"
|
extern "C"
|
||||||
unsigned long FT_StreamRead(FT_Stream stream, unsigned long offset, unsigned char* buffer, unsigned long count)
|
unsigned long FT_StreamRead(FT_Stream stream, unsigned long offset, unsigned char* buffer, unsigned long count)
|
||||||
|
|
@ -66,9 +70,23 @@ namespace Nz
|
||||||
// pour ne libérer FreeType que lorsque plus personne ne l'utilise
|
// pour ne libérer FreeType que lorsque plus personne ne l'utilise
|
||||||
|
|
||||||
public:
|
public:
|
||||||
FreeTypeLibrary() = default;
|
FreeTypeLibrary()
|
||||||
|
{
|
||||||
|
if (FT_Stroker_New(s_library, &s_stroker) != 0)
|
||||||
|
{
|
||||||
|
NazaraWarning("Failed to load FreeType stroker, outline will not be possible");
|
||||||
|
s_stroker = nullptr; //< Just in case
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
~FreeTypeLibrary()
|
~FreeTypeLibrary()
|
||||||
{
|
{
|
||||||
|
if (s_stroker)
|
||||||
|
{
|
||||||
|
FT_Stroker_Done(s_stroker);
|
||||||
|
s_stroker = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
FT_Done_FreeType(s_library);
|
FT_Done_FreeType(s_library);
|
||||||
s_library = nullptr;
|
s_library = nullptr;
|
||||||
}
|
}
|
||||||
|
|
@ -96,7 +114,7 @@ namespace Nz
|
||||||
return FT_Open_Face(s_library, &m_args, -1, nullptr) == 0;
|
return FT_Open_Face(s_library, &m_args, -1, nullptr) == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ExtractGlyph(unsigned int characterSize, char32_t character, TextStyleFlags style, FontGlyph* dst) override
|
bool ExtractGlyph(unsigned int characterSize, char32_t character, TextStyleFlags style, float outlineThickness, FontGlyph* dst) override
|
||||||
{
|
{
|
||||||
#ifdef NAZARA_DEBUG
|
#ifdef NAZARA_DEBUG
|
||||||
if (!dst)
|
if (!dst)
|
||||||
|
|
@ -114,61 +132,85 @@ namespace Nz
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
FT_GlyphSlot& glyph = m_face->glyph;
|
FT_GlyphSlot glyphSlot = m_face->glyph;
|
||||||
|
|
||||||
|
FT_Glyph glyph;
|
||||||
|
if (FT_Get_Glyph(glyphSlot, &glyph) != 0)
|
||||||
|
{
|
||||||
|
NazaraError("Failed to extract glyph");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
CallOnExit destroyGlyph([&]() { FT_Done_Glyph(glyph); });
|
||||||
|
|
||||||
const FT_Pos boldStrength = 2 << 6;
|
const FT_Pos boldStrength = 2 << 6;
|
||||||
|
|
||||||
bool embolden = (style & TextStyle_Bold) != 0;
|
bool embolden = (style & TextStyle_Bold) != 0;
|
||||||
|
bool hasOutlineFormat = (glyph->format == FT_GLYPH_FORMAT_OUTLINE);
|
||||||
|
|
||||||
dst->advance = (embolden) ? boldStrength >> 6 : 0;
|
dst->advance = (embolden) ? boldStrength >> 6 : 0;
|
||||||
|
|
||||||
if (embolden && glyph->format == FT_GLYPH_FORMAT_OUTLINE)
|
if (hasOutlineFormat)
|
||||||
{
|
{
|
||||||
// http://www.freetype.org/freetype2/docs/reference/ft2-outline_processing.html#FT_Outline_Embolden
|
if (embolden)
|
||||||
FT_Outline_Embolden(&glyph->outline, boldStrength);
|
{
|
||||||
embolden = false;
|
// FT_Glyph can be casted to FT_OutlineGlyph if format is FT_GLYPH_FORMAT_OUTLINE
|
||||||
|
FT_OutlineGlyph outlineGlyph = reinterpret_cast<FT_OutlineGlyph>(glyph);
|
||||||
|
if (FT_Outline_Embolden(&outlineGlyph->outline, boldStrength) != 0)
|
||||||
|
{
|
||||||
|
NazaraError("Failed to embolden glyph");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (outlineThickness > 0.f)
|
||||||
|
{
|
||||||
|
FT_Stroker_Set(s_stroker, static_cast<FT_Fixed>(s_scaleFactor * outlineThickness), FT_STROKER_LINECAP_ROUND, FT_STROKER_LINEJOIN_ROUND, 0);
|
||||||
|
if (FT_Glyph_Stroke(&glyph, s_stroker, 1) != 0)
|
||||||
|
{
|
||||||
|
NazaraError("Failed to outline glyph");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// http://www.freetype.org/freetype2/docs/reference/ft2-glyph_management.html#FT_Glyph_To_Bitmap
|
if (FT_Glyph_To_Bitmap(&glyph, FT_RENDER_MODE_NORMAL, nullptr, 1) != 0)
|
||||||
// Conversion du glyphe vers le format bitmap
|
|
||||||
// Cette fonction ne fait rien dans le cas où le glyphe est déjà un bitmap
|
|
||||||
if (FT_Render_Glyph(glyph, FT_RENDER_MODE_NORMAL) != 0)
|
|
||||||
{
|
{
|
||||||
NazaraError("Failed to convert glyph to bitmap");
|
NazaraError("Failed to convert glyph to bitmap");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
FT_Bitmap& bitmap = reinterpret_cast<FT_BitmapGlyph>(glyph)->bitmap;
|
||||||
|
|
||||||
// Dans le cas où nous voulons des caractères gras mais que nous n'avons pas pu agir plus tôt
|
// Dans le cas où nous voulons des caractères gras mais que nous n'avons pas pu agir plus tôt
|
||||||
// nous demandons à FreeType d'agir directement sur le bitmap généré
|
// nous demandons à FreeType d'agir directement sur le bitmap généré
|
||||||
if (embolden)
|
if (embolden)
|
||||||
{
|
{
|
||||||
// http://www.freetype.org/freetype2/docs/reference/ft2-bitmap_handling.html#FT_Bitmap_Embolden
|
// http://www.freetype.org/freetype2/docs/reference/ft2-bitmap_handling.html#FT_Bitmap_Embolden
|
||||||
// "If you want to embolden the bitmap owned by a FT_GlyphSlot_Rec, you should call FT_GlyphSlot_Own_Bitmap on the slot first"
|
FT_Bitmap_Embolden(s_library, &bitmap, boldStrength, boldStrength);
|
||||||
FT_GlyphSlot_Own_Bitmap(glyph);
|
|
||||||
FT_Bitmap_Embolden(s_library, &glyph->bitmap, boldStrength, boldStrength);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
dst->advance += glyph->metrics.horiAdvance >> 6;
|
int outlineThicknessInt = static_cast<int>(outlineThickness * 2.f + 0.5f); //< round it
|
||||||
dst->aabb.x = glyph->metrics.horiBearingX >> 6;
|
dst->advance += glyphSlot->metrics.horiAdvance >> 6;
|
||||||
dst->aabb.y = -(glyph->metrics.horiBearingY >> 6); // Inversion du repère
|
dst->aabb.x = glyphSlot->metrics.horiBearingX >> 6;
|
||||||
dst->aabb.width = glyph->metrics.width >> 6;
|
dst->aabb.y = -(glyphSlot->metrics.horiBearingY >> 6); // Inversion du repère
|
||||||
dst->aabb.height = glyph->metrics.height >> 6;
|
dst->aabb.width = (glyphSlot->metrics.width >> 6) + outlineThicknessInt;
|
||||||
|
dst->aabb.height = (glyphSlot->metrics.height >> 6) + outlineThicknessInt;
|
||||||
|
|
||||||
unsigned int width = glyph->bitmap.width;
|
unsigned int width = bitmap.width;
|
||||||
unsigned int height = glyph->bitmap.rows;
|
unsigned int height = bitmap.rows;
|
||||||
|
|
||||||
if (width > 0 && height > 0)
|
if (width > 0 && height > 0)
|
||||||
{
|
{
|
||||||
dst->image.Create(ImageType_2D, PixelFormatType_A8, width, height);
|
dst->image.Create(ImageType_2D, PixelFormatType_A8, width, height);
|
||||||
UInt8* pixels = dst->image.GetPixels();
|
UInt8* pixels = dst->image.GetPixels();
|
||||||
|
|
||||||
const UInt8* data = glyph->bitmap.buffer;
|
const UInt8* data = bitmap.buffer;
|
||||||
|
|
||||||
// Selon la documentation FreeType, le glyphe peut être encodé en format A8 (huit bits d'alpha par pixel)
|
// Selon la documentation FreeType, le glyphe peut être encodé en format A8 (huit bits d'alpha par pixel)
|
||||||
// ou au format A1 (un bit d'alpha par pixel).
|
// ou au format A1 (un bit d'alpha par pixel).
|
||||||
// Cependant dans un cas comme dans l'autre, il nous faut gérer le pitch (les données peuvent ne pas être contigues)
|
// Cependant dans un cas comme dans l'autre, il nous faut gérer le pitch (les données peuvent ne pas être contigues)
|
||||||
// ainsi que le padding dans le cas du format A1 (Chaque ligne prends un nombre fixe d'octets)
|
// ainsi que le padding dans le cas du format A1 (Chaque ligne prends un nombre fixe d'octets)
|
||||||
if (glyph->bitmap.pixel_mode == FT_PIXEL_MODE_MONO)
|
if (bitmap.pixel_mode == FT_PIXEL_MODE_MONO)
|
||||||
{
|
{
|
||||||
// Format A1
|
// Format A1
|
||||||
for (unsigned int y = 0; y < height; ++y)
|
for (unsigned int y = 0; y < height; ++y)
|
||||||
|
|
@ -176,20 +218,20 @@ namespace Nz
|
||||||
for (unsigned int x = 0; x < width; ++x)
|
for (unsigned int x = 0; x < width; ++x)
|
||||||
*pixels++ = (data[x/8] & ((1 << (7 - x%8)) ? 255 : 0));
|
*pixels++ = (data[x/8] & ((1 << (7 - x%8)) ? 255 : 0));
|
||||||
|
|
||||||
data += glyph->bitmap.pitch;
|
data += bitmap.pitch;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Format A8
|
// Format A8
|
||||||
if (glyph->bitmap.pitch == static_cast<int>(width*sizeof(UInt8))) // Pouvons-nous copier directement ?
|
if (bitmap.pitch == static_cast<int>(width*sizeof(UInt8))) // Pouvons-nous copier directement ?
|
||||||
dst->image.Update(glyph->bitmap.buffer);
|
dst->image.Update(bitmap.buffer); //< Small optimization
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
for (unsigned int y = 0; y < height; ++y)
|
for (unsigned int y = 0; y < height; ++y)
|
||||||
{
|
{
|
||||||
std::memcpy(pixels, data, width*sizeof(UInt8));
|
std::memcpy(pixels, data, width*sizeof(UInt8));
|
||||||
data += glyph->bitmap.pitch;
|
data += bitmap.pitch;
|
||||||
pixels += width*sizeof(UInt8);
|
pixels += width*sizeof(UInt8);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -312,6 +354,11 @@ namespace Nz
|
||||||
m_args.stream = &m_stream;
|
m_args.stream = &m_stream;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool SupportsOutline(float /*outlineThickness*/) const override
|
||||||
|
{
|
||||||
|
return s_stroker != 0;
|
||||||
|
}
|
||||||
|
|
||||||
bool SupportsStyle(TextStyleFlags style) const override
|
bool SupportsStyle(TextStyleFlags style) const override
|
||||||
{
|
{
|
||||||
///TODO
|
///TODO
|
||||||
|
|
|
||||||
|
|
@ -10,9 +10,11 @@ namespace Nz
|
||||||
{
|
{
|
||||||
SimpleTextDrawer::SimpleTextDrawer() :
|
SimpleTextDrawer::SimpleTextDrawer() :
|
||||||
m_color(Color::White),
|
m_color(Color::White),
|
||||||
|
m_outlineColor(Color::Black),
|
||||||
m_style(TextStyle_Regular),
|
m_style(TextStyle_Regular),
|
||||||
m_colorUpdated(true),
|
m_colorUpdated(true),
|
||||||
m_glyphUpdated(true),
|
m_glyphUpdated(true),
|
||||||
|
m_outlineThickness(0.f),
|
||||||
m_characterSize(24)
|
m_characterSize(24)
|
||||||
{
|
{
|
||||||
SetFont(Font::GetDefault());
|
SetFont(Font::GetDefault());
|
||||||
|
|
@ -24,6 +26,8 @@ namespace Nz
|
||||||
m_style(drawer.m_style),
|
m_style(drawer.m_style),
|
||||||
m_colorUpdated(false),
|
m_colorUpdated(false),
|
||||||
m_glyphUpdated(false),
|
m_glyphUpdated(false),
|
||||||
|
m_outlineColor(drawer.m_outlineColor),
|
||||||
|
m_outlineThickness(drawer.m_outlineThickness),
|
||||||
m_characterSize(drawer.m_characterSize)
|
m_characterSize(drawer.m_characterSize)
|
||||||
{
|
{
|
||||||
SetFont(drawer.m_font);
|
SetFont(drawer.m_font);
|
||||||
|
|
@ -120,6 +124,16 @@ namespace Nz
|
||||||
return m_lines.size();
|
return m_lines.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const Color& SimpleTextDrawer::GetOutlineColor() const
|
||||||
|
{
|
||||||
|
return m_outlineColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
float SimpleTextDrawer::GetOutlineThickness() const
|
||||||
|
{
|
||||||
|
return m_outlineThickness;
|
||||||
|
}
|
||||||
|
|
||||||
TextStyleFlags SimpleTextDrawer::GetStyle() const
|
TextStyleFlags SimpleTextDrawer::GetStyle() const
|
||||||
{
|
{
|
||||||
return m_style;
|
return m_style;
|
||||||
|
|
@ -159,6 +173,22 @@ namespace Nz
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SimpleTextDrawer::SetOutlineColor(const Color& color)
|
||||||
|
{
|
||||||
|
m_outlineColor = color;
|
||||||
|
|
||||||
|
m_glyphUpdated = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SimpleTextDrawer::SetOutlineThickness(float thickness)
|
||||||
|
{
|
||||||
|
NazaraAssert(thickness >= 0.f, "Thickness must be zero or positive");
|
||||||
|
|
||||||
|
m_outlineThickness = thickness;
|
||||||
|
|
||||||
|
m_glyphUpdated = false;
|
||||||
|
}
|
||||||
|
|
||||||
void SimpleTextDrawer::SetStyle(TextStyleFlags style)
|
void SimpleTextDrawer::SetStyle(TextStyleFlags style)
|
||||||
{
|
{
|
||||||
m_style = style;
|
m_style = style;
|
||||||
|
|
@ -177,6 +207,8 @@ namespace Nz
|
||||||
{
|
{
|
||||||
m_characterSize = drawer.m_characterSize;
|
m_characterSize = drawer.m_characterSize;
|
||||||
m_color = drawer.m_color;
|
m_color = drawer.m_color;
|
||||||
|
m_outlineColor = drawer.m_outlineColor;
|
||||||
|
m_outlineThickness = drawer.m_outlineThickness;
|
||||||
m_style = drawer.m_style;
|
m_style = drawer.m_style;
|
||||||
m_text = drawer.m_text;
|
m_text = drawer.m_text;
|
||||||
|
|
||||||
|
|
@ -198,6 +230,8 @@ namespace Nz
|
||||||
m_glyphs = std::move(drawer.m_glyphs);
|
m_glyphs = std::move(drawer.m_glyphs);
|
||||||
m_glyphUpdated = std::move(drawer.m_glyphUpdated);
|
m_glyphUpdated = std::move(drawer.m_glyphUpdated);
|
||||||
m_font = std::move(drawer.m_font);
|
m_font = std::move(drawer.m_font);
|
||||||
|
m_outlineColor = std::move(drawer.m_outlineColor);
|
||||||
|
m_outlineThickness = std::move(drawer.m_outlineThickness);
|
||||||
m_style = std::move(drawer.m_style);
|
m_style = std::move(drawer.m_style);
|
||||||
m_text = std::move(drawer.m_text);
|
m_text = std::move(drawer.m_text);
|
||||||
|
|
||||||
|
|
@ -218,6 +252,19 @@ namespace Nz
|
||||||
return drawer;
|
return drawer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SimpleTextDrawer SimpleTextDrawer::Draw(const String& str, unsigned int characterSize, TextStyleFlags style, const Color& color, float outlineThickness, const Color& outlineColor)
|
||||||
|
{
|
||||||
|
SimpleTextDrawer drawer;
|
||||||
|
drawer.SetCharacterSize(characterSize);
|
||||||
|
drawer.SetColor(color);
|
||||||
|
drawer.SetOutlineColor(outlineColor);
|
||||||
|
drawer.SetOutlineThickness(outlineThickness);
|
||||||
|
drawer.SetStyle(style);
|
||||||
|
drawer.SetText(str);
|
||||||
|
|
||||||
|
return drawer;
|
||||||
|
}
|
||||||
|
|
||||||
SimpleTextDrawer SimpleTextDrawer::Draw(Font* font, const String& str, unsigned int characterSize, TextStyleFlags style, const Color& color)
|
SimpleTextDrawer SimpleTextDrawer::Draw(Font* font, const String& str, unsigned int characterSize, TextStyleFlags style, const Color& color)
|
||||||
{
|
{
|
||||||
SimpleTextDrawer drawer;
|
SimpleTextDrawer drawer;
|
||||||
|
|
@ -230,6 +277,20 @@ namespace Nz
|
||||||
return drawer;
|
return drawer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SimpleTextDrawer SimpleTextDrawer::Draw(Font* font, const String& str, unsigned int characterSize, TextStyleFlags style, const Color& color, float outlineThickness, const Color& outlineColor)
|
||||||
|
{
|
||||||
|
SimpleTextDrawer drawer;
|
||||||
|
drawer.SetCharacterSize(characterSize);
|
||||||
|
drawer.SetColor(color);
|
||||||
|
drawer.SetFont(font);
|
||||||
|
drawer.SetOutlineColor(outlineColor);
|
||||||
|
drawer.SetOutlineThickness(outlineThickness);
|
||||||
|
drawer.SetStyle(style);
|
||||||
|
drawer.SetText(str);
|
||||||
|
|
||||||
|
return drawer;
|
||||||
|
}
|
||||||
|
|
||||||
void SimpleTextDrawer::ClearGlyphs() const
|
void SimpleTextDrawer::ClearGlyphs() const
|
||||||
{
|
{
|
||||||
m_bounds.MakeZero();
|
m_bounds.MakeZero();
|
||||||
|
|
@ -278,7 +339,7 @@ namespace Nz
|
||||||
|
|
||||||
const Font::SizeInfo& sizeInfo = m_font->GetSizeInfo(m_characterSize);
|
const Font::SizeInfo& sizeInfo = m_font->GetSizeInfo(m_characterSize);
|
||||||
|
|
||||||
m_glyphs.reserve(m_glyphs.size() + characters.size());
|
m_glyphs.reserve(m_glyphs.size() + characters.size() * (m_outlineThickness > 0.f) ? 2 : 1);
|
||||||
for (char32_t character : characters)
|
for (char32_t character : characters)
|
||||||
{
|
{
|
||||||
if (m_previousCharacter != 0)
|
if (m_previousCharacter != 0)
|
||||||
|
|
@ -304,51 +365,57 @@ namespace Nz
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto GenerateGlyph = [this](Glyph& glyph, char32_t character, float outlineThickness, Nz::Color color, int renderOrder, int* advance)
|
||||||
|
{
|
||||||
|
const Font::Glyph& fontGlyph = m_font->GetGlyph(m_characterSize, m_style, outlineThickness, character);
|
||||||
|
if (fontGlyph.valid && fontGlyph.fauxOutlineThickness <= 0.f)
|
||||||
|
{
|
||||||
|
glyph.atlas = m_font->GetAtlas()->GetLayer(fontGlyph.layerIndex);
|
||||||
|
glyph.atlasRect = fontGlyph.atlasRect;
|
||||||
|
glyph.color = color;
|
||||||
|
glyph.flipped = fontGlyph.flipped;
|
||||||
|
glyph.renderOrder = renderOrder;
|
||||||
|
|
||||||
|
glyph.bounds.Set(fontGlyph.aabb);
|
||||||
|
glyph.bounds.x += m_drawPos.x;
|
||||||
|
glyph.bounds.y += m_drawPos.y;
|
||||||
|
|
||||||
|
// Faux bold and faux outline thickness are not supported
|
||||||
|
|
||||||
|
// We "lean" the glyph to simulate italics style
|
||||||
|
float italic = (fontGlyph.requireFauxItalic) ? 0.208f : 0.f;
|
||||||
|
float italicTop = italic * glyph.bounds.y;
|
||||||
|
float italicBottom = italic * glyph.bounds.GetMaximum().y;
|
||||||
|
|
||||||
|
glyph.corners[0].Set(glyph.bounds.x - italicTop - outlineThickness, glyph.bounds.y - outlineThickness);
|
||||||
|
glyph.corners[1].Set(glyph.bounds.x + glyph.bounds.width - italicTop - outlineThickness, glyph.bounds.y - outlineThickness);
|
||||||
|
glyph.corners[2].Set(glyph.bounds.x - italicBottom - outlineThickness, glyph.bounds.y + glyph.bounds.height - outlineThickness);
|
||||||
|
glyph.corners[3].Set(glyph.bounds.x + glyph.bounds.width - italicBottom - outlineThickness, glyph.bounds.y + glyph.bounds.height - outlineThickness);
|
||||||
|
|
||||||
|
if (advance)
|
||||||
|
*advance = fontGlyph.advance;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
Glyph glyph;
|
Glyph glyph;
|
||||||
if (!whitespace)
|
if (!whitespace)
|
||||||
{
|
{
|
||||||
const Font::Glyph& fontGlyph = m_font->GetGlyph(m_characterSize, m_style, character);
|
if (!GenerateGlyph(glyph, character, 0.f, m_color, 0, &advance))
|
||||||
if (!fontGlyph.valid)
|
|
||||||
continue; // Glyph failed to load, just skip it (can't do much)
|
continue; // Glyph failed to load, just skip it (can't do much)
|
||||||
|
|
||||||
advance = fontGlyph.advance;
|
if (m_outlineThickness > 0.f)
|
||||||
|
|
||||||
glyph.atlas = m_font->GetAtlas()->GetLayer(fontGlyph.layerIndex);
|
|
||||||
glyph.atlasRect = fontGlyph.atlasRect;
|
|
||||||
glyph.color = m_color;
|
|
||||||
glyph.flipped = fontGlyph.flipped;
|
|
||||||
|
|
||||||
glyph.bounds.Set(fontGlyph.aabb);
|
|
||||||
glyph.bounds.x += m_drawPos.x;
|
|
||||||
glyph.bounds.y += m_drawPos.y;
|
|
||||||
|
|
||||||
if (fontGlyph.requireFauxBold)
|
|
||||||
{
|
{
|
||||||
// Let's simulate bold by enlarging the glyph (not a neat idea, but should work)
|
Glyph outlineGlyph;
|
||||||
Vector2f center = glyph.bounds.GetCenter();
|
if (GenerateGlyph(outlineGlyph, character, m_outlineThickness, m_outlineColor, -1, nullptr))
|
||||||
|
{
|
||||||
// Enlarge by 10%
|
m_lines.back().bounds.ExtendTo(outlineGlyph.bounds);
|
||||||
glyph.bounds.width *= 1.1f;
|
m_glyphs.push_back(outlineGlyph);
|
||||||
glyph.bounds.height *= 1.1f;
|
}
|
||||||
|
|
||||||
// Replace it at the correct height
|
|
||||||
Vector2f offset(glyph.bounds.GetCenter() - center);
|
|
||||||
glyph.bounds.x -= offset.x;
|
|
||||||
glyph.bounds.y -= offset.y;
|
|
||||||
|
|
||||||
// Adjust advance (+10%)
|
|
||||||
advance += advance / 10;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// We "lean" the glyph to simulate italics style
|
|
||||||
float italic = (fontGlyph.requireFauxItalic) ? 0.208f : 0.f;
|
|
||||||
float italicTop = italic * glyph.bounds.y;
|
|
||||||
float italicBottom = italic * glyph.bounds.GetMaximum().y;
|
|
||||||
|
|
||||||
glyph.corners[0].Set(glyph.bounds.x - italicTop, glyph.bounds.y);
|
|
||||||
glyph.corners[1].Set(glyph.bounds.x + glyph.bounds.width - italicTop, glyph.bounds.y);
|
|
||||||
glyph.corners[2].Set(glyph.bounds.x - italicBottom, glyph.bounds.y + glyph.bounds.height);
|
|
||||||
glyph.corners[3].Set(glyph.bounds.x + glyph.bounds.width - italicBottom, glyph.bounds.y + glyph.bounds.height);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue