diff --git a/source/graphics/Font.cpp b/source/graphics/Font.cpp index 535a20c4dd..42ade45ec4 100644 --- a/source/graphics/Font.cpp +++ b/source/graphics/Font.cpp @@ -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 -#include +#include #include #include 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(std::lround(value * 64.0f)); +} + +inline float FPosF26Dot6ToFloat(FT_Pos value) +{ + return static_cast(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(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(m_Font->size->metrics.ascender >> SUBPIXEL_SHIFT)}; - const int glyphW{static_cast(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 offsetStroke{GenerateStrokeGlyphBitmap(glyph, codepoint, renderMode, baselineInAtlas)}; + std::optional 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 offsetGlyph{GenerateGlyphBitmap(glyph, codepoint, renderMode, offset, baselineInAtlas)}; + std::optional 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(m_AtlasX) / m_AtlasWidth; gd.v0 = static_cast(m_AtlasY) / m_AtlasHeight; - gd.u1 = static_cast(m_AtlasX - offset.x + glyphW + m_StrokeWidth * 2) / m_AtlasWidth; - gd.v1 = static_cast(m_AtlasY + offset.y + m_Height + m_StrokeWidth * 2) / m_AtlasHeight; + gd.u1 = static_cast(m_AtlasX - offset.X + glyphW + m_StrokeWidth * 2) / m_AtlasWidth; + gd.v1 = static_cast(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::GenerateStrokeGlyphBitmap(const FT_Glyph& glyph, u16 codepoint, FT_Render_Mode renderMode, const int baselineInAtlas) +std::optional 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::GenerateStrokeGlyphBitmap(const FT_Glyph& gl FT_BitmapGlyph bitmapGlyph{reinterpret_cast(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(std::ceil(m_AtlasY + m_StrokeWidth + baselineInAtlas - bitmapGlyph->top))}; + int targetStrokeX{static_cast(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::GenerateStrokeGlyphBitmap(const FT_Glyph& gl return offset; } -std::optional CFont::GenerateGlyphBitmap(FT_Glyph& glyph, u16 codepoint, FT_Render_Mode renderMode, CFont::Offset offset, const int baselineInAtlas) +std::optional 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::GenerateGlyphBitmap(FT_Glyph& glyph, u16 cod FT_BitmapGlyph bitmapGlyph{reinterpret_cast(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(std::ceil(m_AtlasY + offset.Y + m_StrokeWidth + baselineInAtlas - bitmapGlyph->top))}; + int targetX{static_cast(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; } diff --git a/source/graphics/Font.h b/source/graphics/Font.h index a21462d6fc..65cd5861c1 100644 --- a/source/graphics/Font.h +++ b/source/graphics/Font.h @@ -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 #include FT_FREETYPE_H #include FT_STROKER_H -#include FT_OUTLINE_H #include FT_GLYPH_H -#include FT_BITMAP_H +#include #include /** @@ -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, decltype(&ftFaceDeleter)>; using UniqueFTStroker = std::unique_ptr, 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 GenerateStrokeGlyphBitmap(const FT_Glyph& glyph, u16 codepoint, FT_Render_Mode renderMode, const int baselineInAtlas); - std::optional GenerateGlyphBitmap(FT_Glyph& glyph, u16 codepoint, FT_Render_Mode renderMode, Offset offset, const int baselineInAtlas); + std::optional GenerateStrokeGlyphBitmap(const FT_Glyph& glyph, u16 codepoint, FT_Render_Mode renderMode, const float baselineInAtlas); + std::optional 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 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> 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 diff --git a/source/graphics/FontManager.cpp b/source/graphics/FontManager.cpp index d8356b1419..6a13399050 100644 --- a/source/graphics/FontManager.cpp +++ b/source/graphics/FontManager.cpp @@ -103,7 +103,8 @@ CFontManager::CFontManager() std::shared_ptr 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 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; } diff --git a/source/graphics/FontMetrics.cpp b/source/graphics/FontMetrics.cpp index 1026b90efb..8a722e5477 100644 --- a/source/graphics/FontMetrics.cpp +++ b/source/graphics/FontMetrics.cpp @@ -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; diff --git a/source/graphics/FontMetrics.h b/source/graphics/FontMetrics.h index d84bf7f39e..7bb63fe0b7 100644 --- a/source/graphics/FontMetrics.h +++ b/source/graphics/FontMetrics.h @@ -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 m_Font; diff --git a/source/graphics/TextRenderer.cpp b/source/graphics/TextRenderer.cpp index 0fc94c4280..0eb333db83 100644 --- a/source/graphics/TextRenderer.cpp +++ b/source/graphics/TextRenderer.cpp @@ -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::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]); diff --git a/source/gui/CGUISprite.cpp b/source/gui/CGUISprite.cpp index 268b573422..0b326c7889 100644 --- a/source/gui/CGUISprite.cpp +++ b/source/gui/CGUISprite.cpp @@ -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(); +} diff --git a/source/gui/CGUISprite.h b/source/gui/CGUISprite.h index 0cf8aae4c6..6b551df478 100644 --- a/source/gui/CGUISprite.h +++ b/source/gui/CGUISprite.h @@ -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>& 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 diff --git a/source/gui/CGUIText.cpp b/source/gui/CGUIText.cpp index 820a3aea90..777bfbbf1c 100644 --- a/source/gui/CGUIText.cpp +++ b/source/gui/CGUIText.cpp @@ -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); diff --git a/source/gui/GUIRenderer.cpp b/source/gui/GUIRenderer.cpp index 2531f01d60..2d96dead39 100644 --- a/source/gui/GUIRenderer.cpp +++ b/source/gui/GUIRenderer.cpp @@ -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>& Sprites) +void GUIRenderer::UpdateDrawCallCache(const CGUI& pGUI, DrawCalls& Calls, const CStr& SpriteName, const CRect& Size, const bool RoundCoordinates, std::map>& 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); diff --git a/source/gui/GUIRenderer.h b/source/gui/GUIRenderer.h index dc68e41617..fa26b9f12e 100644 --- a/source/gui/GUIRenderer.h +++ b/source/gui/GUIRenderer.h @@ -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>& Sprites); + void UpdateDrawCallCache(const CGUI& pGUI, DrawCalls& Calls, const CStr8& SpriteName, const CRect& Size, const bool RoundCoordinates, std::map>& Sprites); void Draw(DrawCalls& Calls, CCanvas2D& canvas); } diff --git a/source/gui/ObjectBases/IGUITextOwner.cpp b/source/gui/ObjectBases/IGUITextOwner.cpp index 34f0bff3c9..5355ce736b 100644 --- a/source/gui/ObjectBases/IGUITextOwner.cpp +++ b/source/gui/ObjectBases/IGUITextOwner.cpp @@ -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; diff --git a/source/gui/SettingTypes/CGUIString.cpp b/source/gui/SettingTypes/CGUIString.cpp index 4b04dc6031..b6a9646737 100644 --- a/source/gui/SettingTypes/CGUIString.cpp +++ b/source/gui/SettingTypes/CGUIString.cpp @@ -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(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(font.GetHeight()); + size.Height = font.GetHeight(); else - size.Height = static_cast(font.GetLineSpacing()); + size.Height = font.GetLineSpacing(); // Append width, and make maximum height the height. Feedback.m_Size.Width += size.Width; diff --git a/source/gui/tests/test_CGUIText.h b/source/gui/tests/test_CGUIText.h index 731afb913d..f1b8ac0839 100644 --- a/source/gui/tests/test_CGUIText.h +++ b/source/gui/tests/test_CGUIText.h @@ -97,8 +97,8 @@ public: const CStrW font{L"mono-10"}; const CFontMetrics fontMetrics{CStrIntern(font.ToUTF8())}; - const float lineHeight{static_cast(fontMetrics.GetHeight())}; - const float lineSpacing{static_cast(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(fontMetrics.GetHeight())}; - const float lineSpacing{static_cast(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(fontMetrics.GetHeight())}; - const float lineSpacing{static_cast(fontMetrics.GetLineSpacing())}; + const float lineHeight{fontMetrics.GetHeight()}; + const float lineSpacing{fontMetrics.GetLineSpacing()}; CGUIString string; CGUIText text; diff --git a/source/ps/ProfileViewer.cpp b/source/ps/ProfileViewer.cpp index c942110055..8d91731b61 100644 --- a/source/ps/ProfileViewer.cpp +++ b/source/ps/ProfileViewer.cpp @@ -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; diff --git a/source/ps/scripting/JSInterface_Main.cpp b/source/ps/scripting/JSInterface_Main.cpp index 13e6f85915..a06575f14f 100644 --- a/source/ps/scripting/JSInterface_Main.cpp +++ b/source/ps/scripting/JSInterface_Main.cpp @@ -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);