mirror of
https://gitea.wildfiregames.com/0ad/0ad
synced 2026-06-16 05:13:58 -07:00
Add font engine aware of Gui.scale
This commit enhances text rendering by leveraging FreeType and a dynamic font atlas. Previously, GUI scaling relied on bitmap fonts, which led to blurry and distorted text when scaling up. Now, we scale the font size directly using FreeType, resulting in much sharper and more readable text at any GUI scale. Key improvements: - Replaced bitmap font scaling with true font size scaling via FreeType. - Reduced glyph cache dependency on fixed positions, relying on shader scaling instead. - Switched to float-based glyph positioning for more accurate rendering and scaling. These changes ensure clean and consistent text appearance across different GUI scales.
This commit is contained in:
parent
189ec6a7eb
commit
bdd94bd264
16 changed files with 155 additions and 123 deletions
|
|
@ -18,19 +18,32 @@
|
|||
#include "precompiled.h"
|
||||
#include "Font.h"
|
||||
|
||||
#include "graphics/FontManager.h"
|
||||
#include "graphics/TextureManager.h"
|
||||
#include "ps/Filesystem.h"
|
||||
#include "ps/CLogger.h"
|
||||
#include "ps/Profile.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <map>
|
||||
#include <cmath>
|
||||
#include <numeric>
|
||||
#include <string>
|
||||
|
||||
namespace
|
||||
{
|
||||
/**
|
||||
* FreeType represents most of its size and position values in 26.6 fixed-point format — that is,
|
||||
* 26 bits for the integer part and 6 bits for the fractional part.
|
||||
* FreeType's metrics such as: ascender, descender, height, advance, etc. are measured in 1/64th of a pixel.
|
||||
*/
|
||||
inline FT_F26Dot6 FloatToF26Dot6(float value)
|
||||
{
|
||||
return static_cast<FT_F26Dot6>(std::lround(value * 64.0f));
|
||||
}
|
||||
|
||||
inline float FPosF26Dot6ToFloat(FT_Pos value)
|
||||
{
|
||||
return static_cast<float>(value) / 64.0f;
|
||||
}
|
||||
|
||||
struct FTGlyphDeleter {
|
||||
void operator()(FT_Glyph glyph) const
|
||||
{
|
||||
|
|
@ -59,7 +72,15 @@ void CFont::GlyphMap::set(u16 codepoint, const GlyphData& glyph)
|
|||
(*m_Data[codepoint >> 8])[codepoint & 0xff].defined = 1;
|
||||
}
|
||||
|
||||
int CFont::GetCharacterWidth(wchar_t c)
|
||||
float CFont::GetLineSpacing() const {
|
||||
return m_LineSpacing / m_Scale;
|
||||
}
|
||||
|
||||
float CFont::GetHeight() const {
|
||||
return m_Height / m_Scale;
|
||||
}
|
||||
|
||||
float CFont::GetCharacterWidth(wchar_t c)
|
||||
{
|
||||
PROFILE2("GetCharacterWidth font texture generate");
|
||||
const CFont::GlyphData* g{GetGlyph(c)};
|
||||
|
|
@ -67,11 +88,11 @@ int CFont::GetCharacterWidth(wchar_t c)
|
|||
return g ? g->xadvance : 0;
|
||||
}
|
||||
|
||||
void CFont::CalculateStringSize(const wchar_t* string, int& width, int& height)
|
||||
void CFont::CalculateStringSize(const wchar_t* string, float& width, float& height)
|
||||
{
|
||||
PROFILE2("CalculateStringSize font texture generate");
|
||||
width = 0;
|
||||
height = m_Height;
|
||||
height = GetHeight();
|
||||
|
||||
// Compute the width as the width of the longest line.
|
||||
std::wistringstream stream{string};
|
||||
|
|
@ -79,7 +100,7 @@ void CFont::CalculateStringSize(const wchar_t* string, int& width, int& height)
|
|||
while (std::getline(stream, line))
|
||||
{
|
||||
FT_UInt glyphIndexStorage{0};
|
||||
const int lineWidth{std::accumulate(line.begin(), line.end(), 0, [&](int sum, wchar_t c)
|
||||
const float lineWidth{std::accumulate(line.begin(), line.end(), 0.0f, [&](float sum, wchar_t c)
|
||||
{
|
||||
const CFont::GlyphData* g{GetGlyph(c)};
|
||||
|
||||
|
|
@ -101,20 +122,21 @@ void CFont::CalculateStringSize(const wchar_t* string, int& width, int& height)
|
|||
FT_Vector kerning;
|
||||
FT_Get_Kerning(m_Font.get(), prevGlyph, glyphIndex, FT_KERNING_DEFAULT, &kerning);
|
||||
// Add the kerning distance.
|
||||
return sum + g->xadvance + static_cast<int>(kerning.x >> SUBPIXEL_SHIFT);
|
||||
return sum + g->xadvance + FPosF26Dot6ToFloat(kerning.x);
|
||||
})
|
||||
};
|
||||
|
||||
width = std::max(width, lineWidth);
|
||||
height += m_LineSpacing;
|
||||
height += GetLineSpacing();
|
||||
}
|
||||
}
|
||||
|
||||
bool CFont::SetFontFromPath(const std::string& fontPath, const std::string& fontName, int size, int strokeWidth)
|
||||
bool CFont::SetFontFromPath(const std::string& fontPath, const std::string& fontName, float size, float strokeWidth, float scale)
|
||||
{
|
||||
// TODO: expose the Stroke Width outside class.
|
||||
m_StrokeWidth = strokeWidth;
|
||||
m_FontSize = size;
|
||||
m_Scale = scale;
|
||||
m_StrokeWidth = strokeWidth * scale;
|
||||
m_FontSize = size * scale;
|
||||
m_FontName = fontName;
|
||||
|
||||
FT_Face face;
|
||||
|
|
@ -131,7 +153,7 @@ bool CFont::SetFontFromPath(const std::string& fontPath, const std::string& font
|
|||
m_Font.reset(face);
|
||||
|
||||
// Set the font size.
|
||||
if (FT_Error error{FT_Set_Pixel_Sizes(m_Font.get(), 0, m_FontSize)})
|
||||
if (FT_Error error{FT_Set_Char_Size(m_Font.get(), 0, FloatToF26Dot6(m_FontSize), 0 , 0)})
|
||||
{
|
||||
LOGERROR("Failed to set font size %d: %d", size, error);
|
||||
return false;
|
||||
|
|
@ -146,7 +168,7 @@ bool CFont::SetFontFromPath(const std::string& fontPath, const std::string& font
|
|||
return false;
|
||||
}
|
||||
m_Stroker.reset(stroker);
|
||||
FT_Stroker_Set(m_Stroker.get(), m_StrokeWidth * 64, FT_STROKER_LINECAP_ROUND, FT_STROKER_LINEJOIN_ROUND, 0);
|
||||
FT_Stroker_Set(m_Stroker.get(), FloatToF26Dot6(m_StrokeWidth), FT_STROKER_LINECAP_ROUND, FT_STROKER_LINEJOIN_ROUND, 0);
|
||||
}
|
||||
|
||||
if (!ConstructTextureAtlas())
|
||||
|
|
@ -156,10 +178,10 @@ bool CFont::SetFontFromPath(const std::string& fontPath, const std::string& font
|
|||
}
|
||||
|
||||
// Get the height of the font.
|
||||
m_Height = m_Font->size->metrics.height >> SUBPIXEL_SHIFT;
|
||||
m_Height = FPosF26Dot6ToFloat(m_Font->size->metrics.height);
|
||||
|
||||
// Get the line spacing of the font.
|
||||
m_LineSpacing = (m_Font->size->metrics.ascender - m_Font->size->metrics.descender) >> SUBPIXEL_SHIFT;
|
||||
m_LineSpacing = FPosF26Dot6ToFloat(m_Font->size->metrics.ascender - m_Font->size->metrics.descender);
|
||||
|
||||
m_IsLoading = true;
|
||||
|
||||
|
|
@ -265,13 +287,13 @@ const CFont::GlyphData* CFont::ExtractAndGenerateGlyph(u16 codepoint)
|
|||
}
|
||||
UniqueFTGlyph glyphPtr(glyph);
|
||||
|
||||
const int baselineInAtlas{static_cast<int>(m_Font->size->metrics.ascender >> SUBPIXEL_SHIFT)};
|
||||
const int glyphW{static_cast<int>(slot->advance.x >> SUBPIXEL_SHIFT)};
|
||||
const float baselineInAtlas{FPosF26Dot6ToFloat(m_Font->size->metrics.ascender)};
|
||||
const float glyphW{FPosF26Dot6ToFloat(slot->advance.x)};
|
||||
|
||||
if (m_AtlasX + glyphW + m_StrokeWidth + m_AtlasPadding > m_AtlasWidth)
|
||||
{
|
||||
m_AtlasX = 0;
|
||||
m_AtlasY += m_Height + m_AtlasPadding;
|
||||
m_AtlasY += std::ceil(m_Height + m_StrokeWidth + m_AtlasPadding);
|
||||
}
|
||||
|
||||
if (m_AtlasY + m_Height + m_StrokeWidth + m_AtlasPadding > m_AtlasHeight)
|
||||
|
|
@ -281,13 +303,13 @@ const CFont::GlyphData* CFont::ExtractAndGenerateGlyph(u16 codepoint)
|
|||
}
|
||||
|
||||
m_IsDirty = true;
|
||||
CFont::Offset offset{0,0};
|
||||
CVector2D offset{0,0};
|
||||
|
||||
const FT_Render_Mode renderMode{FT_RENDER_MODE_NORMAL};
|
||||
|
||||
if (m_StrokeWidth)
|
||||
{
|
||||
std::optional<CFont::Offset> offsetStroke{GenerateStrokeGlyphBitmap(glyph, codepoint, renderMode, baselineInAtlas)};
|
||||
std::optional<CVector2D> offsetStroke{GenerateStrokeGlyphBitmap(glyph, codepoint, renderMode, baselineInAtlas)};
|
||||
if (!offsetStroke.has_value())
|
||||
{
|
||||
LOGERROR("Failed to generate stroke glyph %u", codepoint);
|
||||
|
|
@ -297,7 +319,7 @@ const CFont::GlyphData* CFont::ExtractAndGenerateGlyph(u16 codepoint)
|
|||
offset = offsetStroke.value();
|
||||
}
|
||||
|
||||
std::optional<CFont::Offset> offsetGlyph{GenerateGlyphBitmap(glyph, codepoint, renderMode, offset, baselineInAtlas)};
|
||||
std::optional<CVector2D> offsetGlyph{GenerateGlyphBitmap(glyph, codepoint, renderMode, offset, baselineInAtlas)};
|
||||
if (!offsetGlyph.has_value())
|
||||
{
|
||||
LOGERROR("Failed to generate glyph %u", codepoint);
|
||||
|
|
@ -308,26 +330,26 @@ const CFont::GlyphData* CFont::ExtractAndGenerateGlyph(u16 codepoint)
|
|||
CFont::GlyphData gd;
|
||||
gd.u0 = static_cast<float>(m_AtlasX) / m_AtlasWidth;
|
||||
gd.v0 = static_cast<float>(m_AtlasY) / m_AtlasHeight;
|
||||
gd.u1 = static_cast<float>(m_AtlasX - offset.x + glyphW + m_StrokeWidth * 2) / m_AtlasWidth;
|
||||
gd.v1 = static_cast<float>(m_AtlasY + offset.y + m_Height + m_StrokeWidth * 2) / m_AtlasHeight;
|
||||
gd.u1 = static_cast<float>(m_AtlasX - offset.X + glyphW + m_StrokeWidth * 2) / m_AtlasWidth;
|
||||
gd.v1 = static_cast<float>(m_AtlasY + offset.Y + m_Height + m_StrokeWidth * 2) / m_AtlasHeight;
|
||||
|
||||
gd.x0 = offset.x - m_StrokeWidth;
|
||||
gd.y0 = - (m_Height + offset.y + m_StrokeWidth);
|
||||
gd.x1 = glyphW + m_StrokeWidth;
|
||||
gd.y1 = m_StrokeWidth;
|
||||
gd.x0 = (offset.X - m_StrokeWidth) / m_Scale;
|
||||
gd.y0 = (-(m_Height + offset.Y + m_StrokeWidth)) / m_Scale;
|
||||
gd.x1 = (glyphW + m_StrokeWidth) / m_Scale;
|
||||
gd.y1 = m_StrokeWidth / m_Scale;
|
||||
|
||||
gd.xadvance = glyphW;
|
||||
gd.xadvance = glyphW / m_Scale;
|
||||
gd.defined = 1;
|
||||
|
||||
m_Glyphs.set(codepoint, gd);
|
||||
|
||||
// Update positions for next glyph.
|
||||
m_AtlasX += glyphW + m_StrokeWidth + m_AtlasPadding;
|
||||
m_AtlasX += std::ceil(glyphW + m_StrokeWidth + m_AtlasPadding);
|
||||
|
||||
return m_Glyphs.get(codepoint);
|
||||
}
|
||||
|
||||
std::optional<CFont::Offset> CFont::GenerateStrokeGlyphBitmap(const FT_Glyph& glyph, u16 codepoint, FT_Render_Mode renderMode, const int baselineInAtlas)
|
||||
std::optional<CVector2D> CFont::GenerateStrokeGlyphBitmap(const FT_Glyph& glyph, u16 codepoint, FT_Render_Mode renderMode, const float baselineInAtlas)
|
||||
{
|
||||
FT_Glyph strokedGlyph;
|
||||
if (FT_Error error{FT_Glyph_Copy(glyph, &strokedGlyph)})
|
||||
|
|
@ -354,17 +376,17 @@ std::optional<CFont::Offset> CFont::GenerateStrokeGlyphBitmap(const FT_Glyph& gl
|
|||
FT_BitmapGlyph bitmapGlyph{reinterpret_cast<FT_BitmapGlyph>(strokedGlyph)};
|
||||
FT_Bitmap& bitmapStroke{bitmapGlyph->bitmap};
|
||||
|
||||
CFont::Offset offset{0,0};
|
||||
int targetStrokeY{m_AtlasY + m_StrokeWidth + baselineInAtlas - bitmapGlyph->top};
|
||||
int targetStrokeX{m_AtlasX + m_StrokeWidth + bitmapGlyph->left};
|
||||
CVector2D offset{0.0f, 0.0f};
|
||||
int targetStrokeY{static_cast<int>(std::ceil(m_AtlasY + m_StrokeWidth + baselineInAtlas - bitmapGlyph->top))};
|
||||
int targetStrokeX{static_cast<int>(std::ceil(m_AtlasX + m_StrokeWidth + bitmapGlyph->left))};
|
||||
if (targetStrokeX < m_AtlasX)
|
||||
{
|
||||
offset.x = bitmapGlyph->left + m_StrokeWidth;
|
||||
offset.X = bitmapGlyph->left + m_StrokeWidth;
|
||||
targetStrokeX = m_AtlasX;
|
||||
}
|
||||
if (targetStrokeY < m_AtlasY)
|
||||
{
|
||||
offset.y = bitmapGlyph->top - baselineInAtlas - m_StrokeWidth;
|
||||
offset.Y = bitmapGlyph->top - baselineInAtlas - m_StrokeWidth;
|
||||
targetStrokeY = m_AtlasY;
|
||||
}
|
||||
BlendGlyphBitmapToTexture(bitmapStroke, targetStrokeX, targetStrokeY, 0, 0, 0);
|
||||
|
|
@ -372,7 +394,7 @@ std::optional<CFont::Offset> CFont::GenerateStrokeGlyphBitmap(const FT_Glyph& gl
|
|||
return offset;
|
||||
}
|
||||
|
||||
std::optional<CFont::Offset> CFont::GenerateGlyphBitmap(FT_Glyph& glyph, u16 codepoint, FT_Render_Mode renderMode, CFont::Offset offset, const int baselineInAtlas)
|
||||
std::optional<CVector2D> CFont::GenerateGlyphBitmap(FT_Glyph& glyph, u16 codepoint, FT_Render_Mode renderMode, CVector2D offset, const float baselineInAtlas)
|
||||
{
|
||||
if (FT_Error error{FT_Glyph_To_Bitmap(&glyph, renderMode, nullptr, 0)})
|
||||
{
|
||||
|
|
@ -382,18 +404,18 @@ std::optional<CFont::Offset> CFont::GenerateGlyphBitmap(FT_Glyph& glyph, u16 cod
|
|||
FT_BitmapGlyph bitmapGlyph{reinterpret_cast<FT_BitmapGlyph>(glyph)};
|
||||
FT_Bitmap& bitmap{bitmapGlyph->bitmap};
|
||||
|
||||
int targetY{m_AtlasY + offset.y + m_StrokeWidth + baselineInAtlas - bitmapGlyph->top};
|
||||
int targetX{m_AtlasX - offset.x + m_StrokeWidth + bitmapGlyph->left};
|
||||
CFont::Offset newOffset{0,0};
|
||||
int targetY{static_cast<int>(std::ceil(m_AtlasY + offset.Y + m_StrokeWidth + baselineInAtlas - bitmapGlyph->top))};
|
||||
int targetX{static_cast<int>(std::ceil(m_AtlasX - offset.X + m_StrokeWidth + bitmapGlyph->left))};
|
||||
CVector2D newOffset{0.0f, 0.0f};
|
||||
if (targetX < m_AtlasX)
|
||||
{
|
||||
newOffset.x = bitmapGlyph->left + m_StrokeWidth;
|
||||
newOffset.X = bitmapGlyph->left + m_StrokeWidth;
|
||||
targetX = m_AtlasX;
|
||||
}
|
||||
|
||||
if (targetY < m_AtlasY)
|
||||
{
|
||||
newOffset.y = bitmapGlyph->top - baselineInAtlas - m_StrokeWidth;
|
||||
newOffset.Y = bitmapGlyph->top - baselineInAtlas - m_StrokeWidth;
|
||||
targetY = m_AtlasY;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
/* Copyright (C) 2025 Wildfire Games.
|
||||
/* Copyright (C) 2025 Wildfire Games.
|
||||
* This file is part of 0 A.D.
|
||||
*
|
||||
* 0 A.D. is free software: you can redistribute it and/or modify
|
||||
|
|
@ -20,15 +20,13 @@
|
|||
|
||||
#include "graphics/Texture.h"
|
||||
#include "maths/Rect.h"
|
||||
#include "ps/CLogger.h"
|
||||
#include "renderer/Renderer.h"
|
||||
|
||||
#include <ft2build.h>
|
||||
#include FT_FREETYPE_H
|
||||
#include FT_STROKER_H
|
||||
#include FT_OUTLINE_H
|
||||
#include FT_GLYPH_H
|
||||
#include FT_BITMAP_H
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
|
||||
/**
|
||||
|
|
@ -44,8 +42,8 @@ public:
|
|||
struct GlyphData
|
||||
{
|
||||
float u0, v0, u1, v1;
|
||||
i16 x0, y0, x1, y1;
|
||||
i16 xadvance;
|
||||
float x0, y0, x1, y1;
|
||||
float xadvance;
|
||||
u8 defined{0};
|
||||
};
|
||||
|
||||
|
|
@ -77,11 +75,11 @@ public:
|
|||
};
|
||||
|
||||
bool HasRGB() const { return m_HasRGB; }
|
||||
int GetLineSpacing() const { return m_LineSpacing; }
|
||||
int GetHeight() const { return m_Height; }
|
||||
int GetCharacterWidth(wchar_t c);
|
||||
int GetStrokeWidth() const { return m_StrokeWidth; }
|
||||
void CalculateStringSize(const wchar_t* string, int& w, int& h);
|
||||
float GetLineSpacing() const;
|
||||
float GetHeight() const;
|
||||
float GetCharacterWidth(wchar_t c);
|
||||
float GetStrokeWidth() const { return m_StrokeWidth; }
|
||||
void CalculateStringSize(const wchar_t* string, float& w, float& h);
|
||||
void GetGlyphBounds(float& x0, float& y0, float& x1, float& y1) const
|
||||
{
|
||||
x0 = m_Bounds.left;
|
||||
|
|
@ -104,22 +102,16 @@ private:
|
|||
using UniqueFTFace = std::unique_ptr<std::remove_pointer_t<FT_Face>, decltype(&ftFaceDeleter)>;
|
||||
using UniqueFTStroker = std::unique_ptr<std::remove_pointer_t<FT_Stroker>, decltype(&ftStrokerDeleter)>;
|
||||
|
||||
struct Offset
|
||||
{
|
||||
int x{0};
|
||||
int y{0};
|
||||
};
|
||||
|
||||
friend class CFontManager;
|
||||
|
||||
bool SetFontFromPath(const std::string& fontPath, const std::string& fontName, int size, int strokeWidth);
|
||||
bool SetFontFromPath(const std::string& fontPath, const std::string& fontName, float size, float strokeWidth, float scale);
|
||||
|
||||
void BlendGlyphBitmapToTexture(const FT_Bitmap& bitmap, int targetX, int targetY, u8 r, u8 g, u8 b);
|
||||
void BlendGlyphBitmapToTextureRGBA(const FT_Bitmap& bitmap, int targetX, int targetY, u8 r, u8 g, u8 b);
|
||||
void BlendGlyphBitmapToTextureR8(const FT_Bitmap& bitmap, int targetX, int targetY);
|
||||
|
||||
std::optional<Offset> GenerateStrokeGlyphBitmap(const FT_Glyph& glyph, u16 codepoint, FT_Render_Mode renderMode, const int baselineInAtlas);
|
||||
std::optional<Offset> GenerateGlyphBitmap(FT_Glyph& glyph, u16 codepoint, FT_Render_Mode renderMode, Offset offset, const int baselineInAtlas);
|
||||
std::optional<CVector2D> GenerateStrokeGlyphBitmap(const FT_Glyph& glyph, u16 codepoint, FT_Render_Mode renderMode, const float baselineInAtlas);
|
||||
std::optional<CVector2D> GenerateGlyphBitmap(FT_Glyph& glyph, u16 codepoint, FT_Render_Mode renderMode, CVector2D offset, const float baselineInAtlas);
|
||||
|
||||
const GlyphData* ExtractAndGenerateGlyph(u16 codepoint);
|
||||
bool ConstructTextureAtlas();
|
||||
|
|
@ -132,10 +124,10 @@ private:
|
|||
|
||||
GlyphMap m_Glyphs;
|
||||
|
||||
int m_LineSpacing;
|
||||
float m_LineSpacing;
|
||||
|
||||
// Height of a capital letter, roughly.
|
||||
int m_Height;
|
||||
float m_Height;
|
||||
|
||||
// Bounding box of all glyphs
|
||||
CRect m_Bounds;
|
||||
|
|
@ -151,14 +143,15 @@ private:
|
|||
int m_AtlasPadding;
|
||||
bool m_IsDirty{false};
|
||||
bool m_IsLoading{false};
|
||||
int m_StrokeWidth{0};
|
||||
float m_StrokeWidth{0.0f};
|
||||
float m_Scale{1.0f};
|
||||
|
||||
std::unique_ptr<u8[]> m_TexData;
|
||||
Renderer::Backend::Format m_TextureFormat{Renderer::Backend::Format::R8G8B8A8_UNORM};
|
||||
int m_TextureFormatStride{4};
|
||||
int m_AtlasSize{0};
|
||||
|
||||
int m_FontSize{0};
|
||||
float m_FontSize{0.0f};
|
||||
std::string m_FontName;
|
||||
std::shared_ptr<std::array<float, 256>> m_GammaCorrectionLUT{nullptr};
|
||||
|
||||
|
|
@ -166,19 +159,12 @@ private:
|
|||
UniqueFTFace m_Font{nullptr, &ftFaceDeleter};
|
||||
UniqueFTStroker m_Stroker{nullptr, &ftStrokerDeleter};
|
||||
|
||||
/**
|
||||
* FreeType represents most of its size and position values in 26.6 fixed-point format — that is,
|
||||
* 26 bits for the integer part and 6 bits for the fractional part.
|
||||
* FreeType's metrics such as: ascender, descender, height, advance, etc. are measured in 1/64th of a pixel.
|
||||
*/
|
||||
static constexpr int SUBPIXEL_SHIFT{6};
|
||||
|
||||
/**
|
||||
* Some fonts are not rendered well at small sizes, so we set a minimum size.
|
||||
* Because we are using a bitmap blending mode, when a font is using small size,
|
||||
* We need to use a different render mode, one with less antialiasing.
|
||||
*/
|
||||
static constexpr int MINIMAL_FONT_SIZE_ANTIALIASING{12};
|
||||
static constexpr float MINIMAL_FONT_SIZE_ANTIALIASING{12.0f};
|
||||
};
|
||||
|
||||
#endif // INCLUDED_FONT
|
||||
|
|
|
|||
|
|
@ -103,7 +103,8 @@ CFontManager::CFontManager()
|
|||
std::shared_ptr<CFont> CFontManager::LoadFont(CStrIntern fontName)
|
||||
{
|
||||
const std::string locale{g_L10n.GetCurrentLocale() != icu::Locale::getUS() ? g_L10n.GetCurrentLocaleString() : ""};
|
||||
CStrIntern localeFontName{locale + fontName.string()};
|
||||
const float guiScale{g_ConfigDB.Get("gui.scale", 1.0f)};
|
||||
CStrIntern localeFontName{fmt::format("{}{}-{}", locale ,fontName.string(), guiScale)};
|
||||
|
||||
FontsMap::iterator it{m_Fonts.find(localeFontName)};
|
||||
if (it != m_Fonts.end())
|
||||
|
|
@ -185,7 +186,7 @@ std::shared_ptr<CFont> CFontManager::LoadFont(CStrIntern fontName)
|
|||
}
|
||||
|
||||
// TODO: For now set strokeWith = 1, later we can expose it to the GUI.
|
||||
if (!font.get()->SetFontFromPath(realPath.string8(), localeFontName.string(), fontSpec.size, fontSpec.stroke ? 1 : 0))
|
||||
if (!font->SetFontFromPath(realPath.string8(), localeFontName.string(), fontSpec.size, fontSpec.stroke ? 1.0f : 0.0f, guiScale))
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
/* Copyright (C) 2013 Wildfire Games.
|
||||
/* Copyright (C) 2025 Wildfire Games.
|
||||
* This file is part of 0 A.D.
|
||||
*
|
||||
* 0 A.D. is free software: you can redistribute it and/or modify
|
||||
|
|
@ -29,7 +29,7 @@ CFontMetrics::CFontMetrics(CStrIntern font)
|
|||
m_Font = g_Renderer.GetFontManager().LoadFont(font);
|
||||
}
|
||||
|
||||
int CFontMetrics::GetLineSpacing() const
|
||||
float CFontMetrics::GetLineSpacing() const
|
||||
{
|
||||
// Return some arbitrary default if the font failed to load, so that the
|
||||
// user of CFontMetrics doesn't have to care about failures
|
||||
|
|
@ -38,21 +38,21 @@ int CFontMetrics::GetLineSpacing() const
|
|||
return m_Font->GetLineSpacing();
|
||||
}
|
||||
|
||||
int CFontMetrics::GetHeight() const
|
||||
float CFontMetrics::GetHeight() const
|
||||
{
|
||||
if (!m_Font)
|
||||
return 6;
|
||||
return m_Font->GetHeight();
|
||||
}
|
||||
|
||||
int CFontMetrics::GetCharacterWidth(wchar_t c) const
|
||||
float CFontMetrics::GetCharacterWidth(wchar_t c) const
|
||||
{
|
||||
if (!m_Font)
|
||||
return 6;
|
||||
return m_Font->GetCharacterWidth(c);
|
||||
}
|
||||
|
||||
void CFontMetrics::CalculateStringSize(const wchar_t* string, int& w, int& h) const
|
||||
void CFontMetrics::CalculateStringSize(const wchar_t* string, float& w, float& h) const
|
||||
{
|
||||
if (!m_Font)
|
||||
w = h = 0;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
/* Copyright (C) 2021 Wildfire Games.
|
||||
/* Copyright (C) 2025 Wildfire Games.
|
||||
* This file is part of 0 A.D.
|
||||
*
|
||||
* 0 A.D. is free software: you can redistribute it and/or modify
|
||||
|
|
@ -31,10 +31,10 @@ class CFontMetrics
|
|||
public:
|
||||
CFontMetrics(CStrIntern font);
|
||||
|
||||
int GetLineSpacing() const;
|
||||
int GetHeight() const;
|
||||
int GetCharacterWidth(wchar_t c) const;
|
||||
void CalculateStringSize(const wchar_t* string, int& w, int& h) const;
|
||||
float GetLineSpacing() const;
|
||||
float GetHeight() const;
|
||||
float GetCharacterWidth(wchar_t c) const;
|
||||
void CalculateStringSize(const wchar_t* string, float& w, float& h) const;
|
||||
|
||||
private:
|
||||
std::shared_ptr<CFont> m_Font;
|
||||
|
|
|
|||
|
|
@ -116,9 +116,9 @@ void CTextRenderer::PutAdvance(const wchar_t* buf)
|
|||
{
|
||||
Put(0.0f, 0.0f, buf);
|
||||
|
||||
int w, h;
|
||||
float w, h;
|
||||
m_Font->CalculateStringSize(buf, w, h);
|
||||
Translate((float)w, 0.0f);
|
||||
Translate(w, 0.0f);
|
||||
}
|
||||
|
||||
void CTextRenderer::Put(float x, float y, const wchar_t* buf)
|
||||
|
|
@ -279,8 +279,8 @@ void CTextRenderer::Render(
|
|||
for (std::list<SBatchRun>::iterator runit = batch.runs.begin(); runit != batch.runs.end(); ++runit)
|
||||
{
|
||||
SBatchRun& run = *runit;
|
||||
i16 x = run.x;
|
||||
i16 y = run.y;
|
||||
float x{run.x};
|
||||
float y{run.y};
|
||||
for (size_t i = 0; i < run.text->size(); ++i)
|
||||
{
|
||||
const CFont::GlyphData* g = batch.font->GetGlyph((*run.text)[i]);
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
/* Copyright (C) 2021 Wildfire Games.
|
||||
/* Copyright (C) 2025 Wildfire Games.
|
||||
* This file is part of 0 A.D.
|
||||
*
|
||||
* 0 A.D. is free software: you can redistribute it and/or modify
|
||||
|
|
@ -30,7 +30,7 @@ void CGUISpriteInstance::Draw(CGUI& pGUI, CCanvas2D& canvas, const CRect& Size,
|
|||
{
|
||||
if (m_CachedSize != Size)
|
||||
{
|
||||
GUIRenderer::UpdateDrawCallCache(pGUI, m_DrawCallCache, m_SpriteName, Size, Sprites);
|
||||
GUIRenderer::UpdateDrawCallCache(pGUI, m_DrawCallCache, m_SpriteName, Size, m_RoundCoordinates, Sprites);
|
||||
m_CachedSize = Size;
|
||||
}
|
||||
GUIRenderer::Draw(m_DrawCallCache, canvas);
|
||||
|
|
@ -45,7 +45,12 @@ CGUISpriteInstance::CGUISpriteInstance()
|
|||
}
|
||||
|
||||
CGUISpriteInstance::CGUISpriteInstance(const CStr& SpriteName)
|
||||
: m_SpriteName(SpriteName)
|
||||
: m_SpriteName(SpriteName), m_RoundCoordinates(true)
|
||||
{
|
||||
}
|
||||
|
||||
CGUISpriteInstance::CGUISpriteInstance(const CStr& SpriteName, const bool RoundCoordinates)
|
||||
: m_SpriteName(SpriteName), m_RoundCoordinates(RoundCoordinates)
|
||||
{
|
||||
}
|
||||
|
||||
|
|
@ -55,3 +60,10 @@ void CGUISpriteInstance::SetName(const CStr& SpriteName)
|
|||
m_CachedSize = CRect();
|
||||
m_DrawCallCache.clear();
|
||||
}
|
||||
|
||||
void CGUISpriteInstance::SetRoundCoordinates(const bool RoundCoordinates)
|
||||
{
|
||||
m_RoundCoordinates = RoundCoordinates;
|
||||
m_CachedSize = CRect();
|
||||
m_DrawCallCache.clear();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
/* Copyright (C) 2022 Wildfire Games.
|
||||
/* Copyright (C) 2025 Wildfire Games.
|
||||
* This file is part of 0 A.D.
|
||||
*
|
||||
* 0 A.D. is free software: you can redistribute it and/or modify
|
||||
|
|
@ -138,6 +138,7 @@ public:
|
|||
|
||||
CGUISpriteInstance();
|
||||
CGUISpriteInstance(const CStr& SpriteName);
|
||||
CGUISpriteInstance(const CStr& SpriteName, const bool RoundCoordinates);
|
||||
|
||||
void Draw(CGUI& pGUI, CCanvas2D& canvas, const CRect& Size, std::map<CStr, std::unique_ptr<const CGUISprite>>& Sprites) const;
|
||||
|
||||
|
|
@ -157,9 +158,17 @@ public:
|
|||
*/
|
||||
void SetName(const CStr& SpriteName);
|
||||
|
||||
/**
|
||||
* Sets the coordinates to be rounded to integer pixels.
|
||||
* This is useful for pixel art, icons in text, to avoid blurry filtering.
|
||||
* Use as rarely as possible, because it clears the draw cache.
|
||||
*/
|
||||
void SetRoundCoordinates(const bool RoundCoordinates);
|
||||
private:
|
||||
CStr m_SpriteName;
|
||||
|
||||
bool m_RoundCoordinates;
|
||||
|
||||
// Stored drawing calls, for more efficient rendering
|
||||
mutable GUIRenderer::DrawCalls m_DrawCallCache;
|
||||
// Relevant details of previously rendered sprite; the cache is invalidated
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
/* Copyright (C) 2023 Wildfire Games.
|
||||
/* Copyright (C) 2025 Wildfire Games.
|
||||
* This file is part of 0 A.D.
|
||||
*
|
||||
* 0 A.D. is free software: you can redistribute it and/or modify
|
||||
|
|
@ -52,7 +52,7 @@ void SGenerateTextImage::SetupSpriteCall(
|
|||
spriteCall.m_Area.right = width - bufferZone;
|
||||
}
|
||||
|
||||
spriteCall.m_Sprite = textureName;
|
||||
spriteCall.m_Sprite = CGUISpriteInstance{textureName, false};
|
||||
|
||||
m_YFrom = spriteCall.m_Area.top - bufferZone;
|
||||
m_YTo = spriteCall.m_Area.bottom + bufferZone;
|
||||
|
|
@ -469,7 +469,7 @@ void CGUIText::Draw(CGUI& pGUI, CCanvas2D& canvas, const CGUIColor& DefaultColor
|
|||
|
||||
textRenderer.SetCurrentColor(tc.m_UseCustomColor ? tc.m_Color : DefaultColor);
|
||||
textRenderer.SetCurrentFont(tc.m_Font);
|
||||
textRenderer.Put(floorf(pos.X + tc.m_Pos.X), floorf(pos.Y + tc.m_Pos.Y), &tc.m_String);
|
||||
textRenderer.Put(pos.X + tc.m_Pos.X, pos.Y + tc.m_Pos.Y, &tc.m_String);
|
||||
}
|
||||
|
||||
canvas.DrawText(textRenderer);
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
/* Copyright (C) 2022 Wildfire Games.
|
||||
/* Copyright (C) 2025 Wildfire Games.
|
||||
* This file is part of 0 A.D.
|
||||
*
|
||||
* 0 A.D. is free software: you can redistribute it and/or modify
|
||||
|
|
@ -56,7 +56,7 @@ DrawCalls& DrawCalls::operator=(const DrawCalls&)
|
|||
}
|
||||
|
||||
|
||||
void GUIRenderer::UpdateDrawCallCache(const CGUI& pGUI, DrawCalls& Calls, const CStr& SpriteName, const CRect& Size, std::map<CStr, std::unique_ptr<const CGUISprite>>& Sprites)
|
||||
void GUIRenderer::UpdateDrawCallCache(const CGUI& pGUI, DrawCalls& Calls, const CStr& SpriteName, const CRect& Size, const bool RoundCoordinates, std::map<CStr, std::unique_ptr<const CGUISprite>>& Sprites)
|
||||
{
|
||||
// This is called only when something has changed (like the size of the
|
||||
// sprite), so it doesn't need to be particularly efficient.
|
||||
|
|
@ -166,7 +166,7 @@ void GUIRenderer::UpdateDrawCallCache(const CGUI& pGUI, DrawCalls& Calls, const
|
|||
LOGERROR("Trying to use a sprite that doesn't exist (\"%s\").", SpriteName.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
it = Sprites.emplace(SpriteName, std::move(sprite)).first;
|
||||
}
|
||||
|
||||
|
|
@ -188,7 +188,7 @@ void GUIRenderer::UpdateDrawCallCache(const CGUI& pGUI, DrawCalls& Calls, const
|
|||
}
|
||||
|
||||
Call.m_Vertices = ObjectSize;
|
||||
if ((*cit)->m_RoundCoordinates)
|
||||
if ((*cit)->m_RoundCoordinates && RoundCoordinates)
|
||||
{
|
||||
// Round the vertex coordinates to integers, to avoid ugly filtering artifacts
|
||||
Call.m_Vertices.left = (int)(Call.m_Vertices.left + 0.5f);
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
/* Copyright (C) 2022 Wildfire Games.
|
||||
/* Copyright (C) 2025 Wildfire Games.
|
||||
* This file is part of 0 A.D.
|
||||
*
|
||||
* 0 A.D. is free software: you can redistribute it and/or modify
|
||||
|
|
@ -64,7 +64,7 @@ namespace GUIRenderer
|
|||
DrawCalls& operator=(const DrawCalls&);
|
||||
};
|
||||
|
||||
void UpdateDrawCallCache(const CGUI& pGUI, DrawCalls& Calls, const CStr8& SpriteName, const CRect& Size, std::map<CStr8, std::unique_ptr<const CGUISprite>>& Sprites);
|
||||
void UpdateDrawCallCache(const CGUI& pGUI, DrawCalls& Calls, const CStr8& SpriteName, const CRect& Size, const bool RoundCoordinates, std::map<CStr8, std::unique_ptr<const CGUISprite>>& Sprites);
|
||||
|
||||
void Draw(DrawCalls& Calls, CCanvas2D& canvas);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
/* Copyright (C) 2021 Wildfire Games.
|
||||
/* Copyright (C) 2025 Wildfire Games.
|
||||
* This file is part of 0 A.D.
|
||||
*
|
||||
* 0 A.D. is free software: you can redistribute it and/or modify
|
||||
|
|
@ -113,7 +113,7 @@ void IGUITextOwner::CalculateTextPosition(CRect& ObjSize, CVector2D& TextPos, CG
|
|||
break;
|
||||
case EVAlign::CENTER:
|
||||
// Round to integer pixel values, else the fonts look awful
|
||||
TextPos.Y = floorf(ObjSize.CenterPoint().Y - Text.GetSize().Height / 2.f);
|
||||
TextPos.Y = ObjSize.CenterPoint().Y - Text.GetSize().Height / 2.f;
|
||||
break;
|
||||
case EVAlign::BOTTOM:
|
||||
TextPos.Y = ObjSize.bottom - Text.GetSize().Height;
|
||||
|
|
|
|||
|
|
@ -157,7 +157,7 @@ void CGUIString::GenerateTextCall(const CGUI& pGUI, SFeedback& Feedback, CStrInt
|
|||
TextCall.m_Tooltip = tagAttrib.value;
|
||||
}
|
||||
|
||||
SpriteCall.m_Sprite = icon.m_SpriteName;
|
||||
SpriteCall.m_Sprite = CGUISpriteInstance{icon.m_SpriteName,false};
|
||||
|
||||
// Add sprite call
|
||||
Feedback.m_SpriteCalls.push_back(std::move(SpriteCall));
|
||||
|
|
@ -209,17 +209,17 @@ void CGUIString::GenerateTextCall(const CGUI& pGUI, SFeedback& Feedback, CStrInt
|
|||
|
||||
// Calculate the size of the font.
|
||||
CSize2D size;
|
||||
int cx, cy;
|
||||
float cx, cy;
|
||||
CFontMetrics font (TextCall.m_Font);
|
||||
font.CalculateStringSize(TextCall.m_String.c_str(), cx, cy);
|
||||
|
||||
size.Width = static_cast<float>(cx);
|
||||
size.Width = cx;
|
||||
// For anything other than the first line, the line spacing
|
||||
// needs to be considered rather than just the height of the text.
|
||||
if (FirstLine)
|
||||
size.Height = static_cast<float>(font.GetHeight());
|
||||
size.Height = font.GetHeight();
|
||||
else
|
||||
size.Height = static_cast<float>(font.GetLineSpacing());
|
||||
size.Height = font.GetLineSpacing();
|
||||
|
||||
// Append width, and make maximum height the height.
|
||||
Feedback.m_Size.Width += size.Width;
|
||||
|
|
|
|||
|
|
@ -97,8 +97,8 @@ public:
|
|||
const CStrW font{L"mono-10"};
|
||||
const CFontMetrics fontMetrics{CStrIntern(font.ToUTF8())};
|
||||
|
||||
const float lineHeight{static_cast<float>(fontMetrics.GetHeight())};
|
||||
const float lineSpacing{static_cast<float>(fontMetrics.GetLineSpacing())};
|
||||
const float lineHeight{fontMetrics.GetHeight()};
|
||||
const float lineSpacing{fontMetrics.GetLineSpacing()};
|
||||
|
||||
CGUIString string;
|
||||
CGUIText text;
|
||||
|
|
@ -208,10 +208,12 @@ public:
|
|||
const CStrW font{L"mono-10"};
|
||||
CFontMetrics fontMetrics{CStrIntern(font.ToUTF8())};
|
||||
|
||||
int firstWordWidth = 0, firstWordHeight = 0;
|
||||
float firstWordWidth{0};
|
||||
float firstWordHeight{0};
|
||||
fontMetrics.CalculateStringSize(firstWord.c_str(), firstWordWidth, firstWordHeight);
|
||||
TS_ASSERT(firstWordWidth > 0);
|
||||
int secondWordWidth = 0, secondWordHeight = 0;
|
||||
float secondWordWidth{0};
|
||||
float secondWordHeight{0};
|
||||
fontMetrics.CalculateStringSize(secondWord.c_str(), secondWordWidth, secondWordHeight);
|
||||
TS_ASSERT(secondWordWidth > 0);
|
||||
TS_ASSERT(firstWordWidth < secondWordWidth);
|
||||
|
|
@ -283,8 +285,8 @@ public:
|
|||
|
||||
const CStrW font{L"mono-10"};
|
||||
const CFontMetrics fontMetrics{CStrIntern(font.ToUTF8())};
|
||||
const float lineHeight{static_cast<float>(fontMetrics.GetHeight())};
|
||||
const float lineSpacing{static_cast<float>(fontMetrics.GetLineSpacing())};
|
||||
const float lineHeight{fontMetrics.GetHeight()};
|
||||
const float lineSpacing{fontMetrics.GetLineSpacing()};
|
||||
|
||||
float renderedWidth = 0.f;
|
||||
const float width = 200.f;
|
||||
|
|
@ -352,8 +354,8 @@ public:
|
|||
|
||||
const CStrW font{L"mono-10"};
|
||||
const CFontMetrics fontMetrics{CStrIntern(font.ToUTF8())};
|
||||
const float lineHeight{static_cast<float>(fontMetrics.GetHeight())};
|
||||
const float lineSpacing{static_cast<float>(fontMetrics.GetLineSpacing())};
|
||||
const float lineHeight{fontMetrics.GetHeight()};
|
||||
const float lineSpacing{fontMetrics.GetLineSpacing()};
|
||||
|
||||
CGUIString string;
|
||||
CGUIText text;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
/* Copyright (C) 2024 Wildfire Games.
|
||||
/* Copyright (C) 2025 Wildfire Games.
|
||||
* This file is part of 0 A.D.
|
||||
*
|
||||
* 0 A.D. is free software: you can redistribute it and/or modify
|
||||
|
|
@ -195,7 +195,7 @@ void CProfileViewer::RenderProfile(CCanvas2D& canvas)
|
|||
for (size_t col = 0; col < columns.size(); ++col)
|
||||
{
|
||||
CStrW text = columns[col].title.FromUTF8();
|
||||
int w, h;
|
||||
float w, h;
|
||||
font.CalculateStringSize(text.c_str(), w, h);
|
||||
|
||||
float x = colX;
|
||||
|
|
@ -228,7 +228,7 @@ void CProfileViewer::RenderProfile(CCanvas2D& canvas)
|
|||
for (size_t col = 0; col < columns.size(); ++col)
|
||||
{
|
||||
CStrW text = table->GetCellText(row, col).FromUTF8();
|
||||
int w, h;
|
||||
float w, h;
|
||||
font.CalculateStringSize(text.c_str(), w, h);
|
||||
|
||||
float x = rowColX;
|
||||
|
|
|
|||
|
|
@ -87,8 +87,8 @@ int GetFps()
|
|||
|
||||
CSize2D GetTextSize(const std::string& fontName, const std::wstring& text)
|
||||
{
|
||||
int width = 0;
|
||||
int height = 0;
|
||||
float width = 0;
|
||||
float height = 0;
|
||||
CStrIntern _fontName(fontName);
|
||||
CFontMetrics fontMetrics(_fontName);
|
||||
fontMetrics.CalculateStringSize(text.c_str(), width, height);
|
||||
|
|
|
|||
Loading…
Reference in a new issue