From dfb45981860184dc407437672a12904cf6b08a0b Mon Sep 17 00:00:00 2001 From: trompetin17 Date: Wed, 28 May 2025 22:37:37 -0500 Subject: [PATCH] Align font height, capHeight to FreeType metrics Previously, `height` was derived from a manually chosen glyph (typically "I", Standard Cap Height) using FontBuilder, and `lineSpacing` was used inconsistently for layout logic as a height. Now, with the FreeType-based system: - `height` uses `face->size->metrics.height`, which includes the recommended line height with internal leading/line gap as defined by the font designer. - `lineSpacing` was removed - `GetCapHeight` uses the standard cap height + ascender to have a visual virtual alignment This change standardizes font metric usage: - Use `height` for vertical layout and line progression. - Use `GetCapheight` in layout engines like `CGUIText` or `CGUIString`, This ensures better alignment across fonts and consistent spacing in multiline text rendering. Fixes: #7962 --- source/graphics/Font.cpp | 32 ++++++++++++++++---------- source/graphics/Font.h | 13 ++++++++--- source/graphics/FontMetrics.cpp | 16 ++++++------- source/graphics/FontMetrics.h | 2 +- source/gui/ObjectTypes/CInput.cpp | 21 ++++++++--------- source/gui/SettingTypes/CGUIString.cpp | 5 ++-- source/gui/tests/test_CGUIText.h | 26 ++++++++++----------- source/ps/CConsole.cpp | 19 +++++++-------- source/ps/CConsole.h | 6 ++--- source/ps/CLogger.cpp | 10 ++++---- source/ps/ProfileViewer.cpp | 16 ++++++------- 11 files changed, 89 insertions(+), 77 deletions(-) diff --git a/source/graphics/Font.cpp b/source/graphics/Font.cpp index 1100029bc0..a568b1d691 100644 --- a/source/graphics/Font.cpp +++ b/source/graphics/Font.cpp @@ -28,6 +28,7 @@ #include #include #include +#include namespace { @@ -74,14 +75,16 @@ void CFont::GlyphMap::set(u16 codepoint, const GlyphData& glyph) (*m_Data[codepoint >> 8])[codepoint & 0xff].defined = 1; } -float CFont::GetLineSpacing() const { - return m_LineSpacing / m_Scale; -} - float CFont::GetHeight() const { return m_Height / m_Scale; } +float CFont::GetCapHeight() +{ + const CFont::GlyphData* g{GetGlyph(L'I')}; + return (g ? g->yadvance : 0) + std::abs(FPosF26Dot6ToFloat(m_Faces.front()->size->metrics.descender)); +} + float CFont::GetCharacterWidth(wchar_t c) { PROFILE2("GetCharacterWidth font texture generate"); @@ -94,11 +97,13 @@ void CFont::CalculateStringSize(const wchar_t* string, float& width, float& heig { PROFILE2("CalculateStringSize font texture generate"); width = 0; - height = GetHeight(); + height = 0; // Compute the width as the width of the longest line. + std::wstring original{string}; std::wistringstream stream{string}; std::wstring line; + bool firstLine{true}; while (std::getline(stream, line)) { FT_UInt glyphIndexStorage{0}; @@ -129,8 +134,15 @@ void CFont::CalculateStringSize(const wchar_t* string, float& width, float& heig }; width = std::max(width, lineWidth); - height += GetLineSpacing(); + + if (!firstLine || !line.empty()) + height += firstLine ? GetCapHeight() : GetHeight(); + + firstLine = false; } + + if (original.back() == L'\n') + height += GetHeight(); } bool CFont::SetFontParams(const std::string& fontName, float size, float strokeWidth, float scale) @@ -204,15 +216,10 @@ bool CFont::AddFontFromPath(const OsPath& fontPath) return false; } + // Get the height of the font. if(m_Faces.empty()) - { - // Get the height of the font. m_Height = FPosF26Dot6ToFloat(face->size->metrics.height); - // Get the line spacing of the font. - m_LineSpacing = FPosF26Dot6ToFloat(face->size->metrics.ascender - face->size->metrics.descender); - } - // Add the fallback font to the list. m_Faces.push_back({face, &ftFaceDeleter}); @@ -385,6 +392,7 @@ const CFont::GlyphData* CFont::ExtractAndGenerateGlyph(u16 codepoint) gd.y1 = m_StrokeWidth / m_Scale; gd.xadvance = glyphW / m_Scale; + gd.yadvance = FPosF26Dot6ToFloat(slot->metrics.height) / m_Scale; gd.defined = 1; gd.face = faceToUse; diff --git a/source/graphics/Font.h b/source/graphics/Font.h index 77dc6335b5..5d0c044e6e 100644 --- a/source/graphics/Font.h +++ b/source/graphics/Font.h @@ -45,6 +45,7 @@ public: float u0, v0, u1, v1; float x0, y0, x1, y1; float xadvance; + float yadvance; u8 defined{0}; FT_Face face; }; @@ -77,8 +78,16 @@ public: }; bool HasRGB() const { return m_HasRGB; } - float GetLineSpacing() const; float GetHeight() const; + + /** + * In typography, cap height is the height of a capital letter above the baseline for a particular typeface. + * It specifically is the height of capital letters that are flat—such as H or I—as opposed to round letters such as O, or pointed letters like A, + * both of which may display overshoot. The height of the small letters is the x-height. + * REf: https://en.wikipedia.org/wiki/Cap_height + */ + float GetCapHeight(); + float GetCharacterWidth(wchar_t c); float GetStrokeWidth() const { return m_StrokeWidth; } void CalculateStringSize(const wchar_t* string, float& w, float& h); @@ -127,8 +136,6 @@ private: GlyphMap m_Glyphs; - float m_LineSpacing; - // Height of a capital letter, roughly. float m_Height; diff --git a/source/graphics/FontMetrics.cpp b/source/graphics/FontMetrics.cpp index 8a722e5477..3b8e1690e7 100644 --- a/source/graphics/FontMetrics.cpp +++ b/source/graphics/FontMetrics.cpp @@ -29,15 +29,6 @@ CFontMetrics::CFontMetrics(CStrIntern font) m_Font = g_Renderer.GetFontManager().LoadFont(font); } -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 - if (!m_Font) - return 12; - return m_Font->GetLineSpacing(); -} - float CFontMetrics::GetHeight() const { if (!m_Font) @@ -59,3 +50,10 @@ void CFontMetrics::CalculateStringSize(const wchar_t* string, float& w, float& h else m_Font->CalculateStringSize(string, w, h); } + +float CFontMetrics::GetCapHeight() +{ + if (!m_Font) + return 0.0f; + return m_Font->GetCapHeight(); +} diff --git a/source/graphics/FontMetrics.h b/source/graphics/FontMetrics.h index 7bb63fe0b7..fe4206468b 100644 --- a/source/graphics/FontMetrics.h +++ b/source/graphics/FontMetrics.h @@ -31,8 +31,8 @@ class CFontMetrics public: CFontMetrics(CStrIntern font); - float GetLineSpacing() const; float GetHeight() const; + float GetCapHeight(); float GetCharacterWidth(wchar_t c) const; void CalculateStringSize(const wchar_t* string, float& w, float& h) const; diff --git a/source/gui/ObjectTypes/CInput.cpp b/source/gui/ObjectTypes/CInput.cpp index 13145ca6ac..c3abb84fb5 100644 --- a/source/gui/ObjectTypes/CInput.cpp +++ b/source/gui/ObjectTypes/CInput.cpp @@ -33,6 +33,8 @@ #include "ps/Hotkey.h" #include +#include +#include extern int g_yres; @@ -1232,8 +1234,8 @@ void CInput::DrawContent(CCanvas2D& canvas) } // Get the height of this font. - float h = (float)font.GetHeight(); - float ls = (float)font.GetLineSpacing(); + const float h{font.GetCapHeight()}; + const float ls{font.GetHeight()}; CTextRenderer textRenderer; textRenderer.SetCurrentFont(font_name); @@ -1880,7 +1882,7 @@ void CInput::UpdateText(int from, int to_before, int to_after) if (m_ScrollBar) { - GetScrollBar(0).SetScrollRange(m_CharacterPositions.size() * font.GetLineSpacing() + m_BufferZone * 2.f); + GetScrollBar(0).SetScrollRange(m_CharacterPositions.size() * font.GetHeight() + m_BufferZone * 2.f); GetScrollBar(0).SetScrollSpace(m_CachedActualSize.GetHeight()); } } @@ -1903,10 +1905,8 @@ int CInput::GetMouseHoveringTextPosition() const if (m_ScrollBar) scroll = GetScrollBarPos(0); - // Now get the height of the font. - // TODO: Get the real font - CFontMetrics font(CStrIntern(m_Font->ToUTF8())); - float spacing = (float)font.GetLineSpacing(); + const CFontMetrics font{CStrIntern{m_Font->ToUTF8()}}; + const float spacing{font.GetHeight()}; // Change mouse position relative to text. mouse -= m_CachedActualSize.TopLeft(); @@ -2032,11 +2032,8 @@ void CInput::UpdateAutoScroll() const float scroll = GetScrollBar(0).GetPos(); - // Now get the height of the font. - // TODO: Get the real font - CFontMetrics font(CStrIntern(m_Font->ToUTF8())); - float spacing = (float)font.GetLineSpacing(); - //float height = font.GetHeight(); + const CFontMetrics font{CStrIntern{m_Font->ToUTF8()}}; + const float spacing{font.GetHeight()}; // TODO Gee (2004-11-21): Okay, I need a 'std::list' for some reasons, but I would really like to // be able to get the specific element here. This is hopefully a temporary hack. diff --git a/source/gui/SettingTypes/CGUIString.cpp b/source/gui/SettingTypes/CGUIString.cpp index b6a9646737..6edb5a6cda 100644 --- a/source/gui/SettingTypes/CGUIString.cpp +++ b/source/gui/SettingTypes/CGUIString.cpp @@ -27,6 +27,7 @@ #include #include +#include // List of word delimiter bounds // The list contains ranges of word delimiters. The odd indexed chars are the start @@ -217,9 +218,9 @@ void CGUIString::GenerateTextCall(const CGUI& pGUI, SFeedback& Feedback, CStrInt // 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 = font.GetHeight(); + size.Height = font.GetCapHeight(); else - size.Height = font.GetLineSpacing(); + size.Height = font.GetHeight(); // 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 f1b8ac0839..19beb26a8b 100644 --- a/source/gui/tests/test_CGUIText.h +++ b/source/gui/tests/test_CGUIText.h @@ -95,10 +95,10 @@ public: CGUI gui{*g_ScriptContext}; const CStrW font{L"mono-10"}; - const CFontMetrics fontMetrics{CStrIntern(font.ToUTF8())}; + CFontMetrics fontMetrics{CStrIntern(font.ToUTF8())}; - const float lineHeight{fontMetrics.GetHeight()}; - const float lineSpacing{fontMetrics.GetLineSpacing()}; + const float lineHeight{fontMetrics.GetCapHeight()}; + const float lineSpacing{fontMetrics.GetHeight()}; CGUIString string; CGUIText text; @@ -219,8 +219,8 @@ public: TS_ASSERT(firstWordWidth < secondWordWidth); const float spaceWidth = fontMetrics.GetCharacterWidth(L' '); - const float lineHeight = fontMetrics.GetHeight(); - const float lineSpacing = fontMetrics.GetLineSpacing(); + const float lineHeight{fontMetrics.GetCapHeight()}; + const float lineSpacing{fontMetrics.GetHeight()}; auto layoutText = [&gui, &string, &font, lineHeight, firstWordWidth](const float width) { @@ -284,9 +284,9 @@ public: CGUI gui{*g_ScriptContext}; const CStrW font{L"mono-10"}; - const CFontMetrics fontMetrics{CStrIntern(font.ToUTF8())}; - const float lineHeight{fontMetrics.GetHeight()}; - const float lineSpacing{fontMetrics.GetLineSpacing()}; + CFontMetrics fontMetrics{CStrIntern(font.ToUTF8())}; + const float lineHeight{fontMetrics.GetCapHeight()}; + const float lineSpacing{fontMetrics.GetHeight()}; float renderedWidth = 0.f; const float width = 200.f; @@ -334,8 +334,8 @@ public: const CStrW font{L"sans-bold-13"}; CFontMetrics fontMetrics{CStrIntern(font.ToUTF8())}; - const float lineHeight = fontMetrics.GetHeight(); - const float lineSpacing = fontMetrics.GetLineSpacing(); + const float lineHeight{fontMetrics.GetCapHeight()}; + const float lineSpacing{fontMetrics.GetHeight()}; CGUIString string; CGUIText text; @@ -353,9 +353,9 @@ public: CGUI gui{*g_ScriptContext}; const CStrW font{L"mono-10"}; - const CFontMetrics fontMetrics{CStrIntern(font.ToUTF8())}; - const float lineHeight{fontMetrics.GetHeight()}; - const float lineSpacing{fontMetrics.GetLineSpacing()}; + CFontMetrics fontMetrics{CStrIntern(font.ToUTF8())}; + const float lineHeight{fontMetrics.GetCapHeight()}; + const float lineSpacing{fontMetrics.GetHeight()}; CGUIString string; CGUIText text; diff --git a/source/ps/CConsole.cpp b/source/ps/CConsole.cpp index c7e68ae24b..030669fba6 100644 --- a/source/ps/CConsole.cpp +++ b/source/ps/CConsole.cpp @@ -42,6 +42,7 @@ #include "scriptinterface/JSON.h" #include +#include #include #include #include @@ -95,19 +96,19 @@ void CConsole::Init() // Calculate and store the line spacing const CFontMetrics font{CStrIntern(m_consoleFont)}; - m_FontHeight = font.GetLineSpacing(); + m_FontHeight = font.GetHeight(); m_FontWidth = font.GetCharacterWidth(L'C'); m_CharsPerPage = static_cast(g_xres / m_FontWidth); // Offset by an arbitrary amount, to make it fit more nicely - m_FontOffset = 7; + m_FontOffset = 7.0f; m_CursorBlinkRate = g_ConfigDB.Get("gui.cursorblinkrate", 0.5); } void CConsole::UpdateScreenSize(int w, int h) { - m_X = 0; - m_Y = 0; + m_X = 0.0f; + m_Y = 0.0f; float height = h * 0.6f; m_Width = w / g_VideoMode.GetScale(); m_Height = height / g_VideoMode.GetScale(); @@ -230,8 +231,8 @@ void CConsole::DrawWindow(CCanvas2D& canvas) if (m_Height > m_FontHeight + 4) { points = { - CVector2D{0.0f, m_Height - static_cast(m_FontHeight) - 4.0f}, - CVector2D{m_Width, m_Height - static_cast(m_FontHeight) - 4.0f} + CVector2D{0.0f, m_Height - m_FontHeight - 4.0f}, + CVector2D{m_Width, m_Height - m_FontHeight - 4.0f} }; for (CVector2D& point : points) point += CVector2D{m_X, m_Y - (1.0f - m_VisibleFrac) * m_Height}; @@ -258,7 +259,7 @@ void CConsole::DrawHistory(CTextRenderer& textRenderer) { textRenderer.Put( 9.0f, - m_Height - static_cast(m_FontOffset) - static_cast(m_FontHeight) * (i - m_MsgHistPos + 1), + m_Height - m_FontOffset - m_FontHeight * (i - m_MsgHistPos + 1), it->c_str()); } @@ -274,7 +275,7 @@ void CConsole::DrawBuffer(CTextRenderer& textRenderer) const CVector2D savedTranslate = textRenderer.GetTranslate(); - textRenderer.Translate(2.0f, m_Height - static_cast(m_FontOffset) + 1.0f); + textRenderer.Translate(2.0f, m_Height - m_FontOffset + 1.0f); textRenderer.SetCurrentColor(CColor(1.0f, 1.0f, 0.0f, 1.0f)); textRenderer.PutAdvance(L"]"); @@ -415,7 +416,7 @@ void CConsole::InsertChar(const int szChar, const wchar_t cooked) { std::lock_guard lock(m_Mutex); // needed for safe access to m_deqMsgHistory - const int linesShown = static_cast(m_Height / m_FontHeight) - 4; + const int linesShown = static_cast(std::ceil(m_Height / m_FontHeight)) - 4; m_MsgHistPos = Clamp(static_cast(m_MsgHistory.size()) - linesShown, 1, static_cast(m_MsgHistory.size())); } else diff --git a/source/ps/CConsole.h b/source/ps/CConsole.h index e6e4ed5d3c..ea5ed41d4b 100644 --- a/source/ps/CConsole.h +++ b/source/ps/CConsole.h @@ -78,9 +78,9 @@ private: // Lock for all state modified by InsertMessage std::mutex m_Mutex; - int m_FontHeight; - int m_FontWidth; - int m_FontOffset; // distance to move up before drawing + float m_FontHeight; + float m_FontWidth; + float m_FontOffset; // distance to move up before drawing size_t m_CharsPerPage; float m_X; diff --git a/source/ps/CLogger.cpp b/source/ps/CLogger.cpp index cc2bb0767c..0889bdbda3 100644 --- a/source/ps/CLogger.cpp +++ b/source/ps/CLogger.cpp @@ -173,16 +173,16 @@ void CLogger::Render(CCanvas2D& canvas) CleanupRenderQueue(); - CStrIntern font_name("mono-stroke-10"); - CFontMetrics font(font_name); - int lineSpacing = font.GetLineSpacing(); + CStrIntern font_name{"mono-stroke-10"}; + CFontMetrics font{font_name}; + float height{font.GetHeight()}; CTextRenderer textRenderer; textRenderer.SetCurrentFont(font_name); textRenderer.SetCurrentColor(CColor(1.0f, 1.0f, 1.0f, 1.0f)); // Offset by an extra 35px vertically to avoid the top bar. - textRenderer.Translate(4.0f, 35.0f + lineSpacing); + textRenderer.Translate(4.0f, 35.0f + height); // (Lock must come after loading the CFont, since that might log error messages // and attempt to lock the mutex recursively which is forbidden) @@ -216,7 +216,7 @@ void CLogger::Render(CCanvas2D& canvas) textRenderer.ResetTranslate(savedTranslate); - textRenderer.Translate(0.0f, (float)lineSpacing); + textRenderer.Translate(0.0f, height); } canvas.DrawText(textRenderer); diff --git a/source/ps/ProfileViewer.cpp b/source/ps/ProfileViewer.cpp index 8d91731b61..005064b2a2 100644 --- a/source/ps/ProfileViewer.cpp +++ b/source/ps/ProfileViewer.cpp @@ -162,7 +162,7 @@ void CProfileViewer::RenderProfile(CCanvas2D& canvas) CStrIntern font_name("mono-stroke-10"); CFontMetrics font(font_name); - int lineSpacing = font.GetLineSpacing(); + int height = font.GetHeight(); // Render background. float estimateWidth = 50.0f; @@ -172,7 +172,7 @@ void CProfileViewer::RenderProfile(CCanvas2D& canvas) float estimateHeight = 3 + static_cast(numrows); if (m->path.size() > 1) estimateHeight += 2; - estimateHeight *= lineSpacing; + estimateHeight *= height; canvas.DrawRect(CRect(CSize2D(estimateWidth, estimateHeight)), CColor(0.0f, 0.0f, 0.0f, 0.5f)); @@ -180,7 +180,7 @@ void CProfileViewer::RenderProfile(CCanvas2D& canvas) for (size_t row = 0; row < numrows; ++row) { canvas.DrawRect( - CRect(CVector2D(0.0f, lineSpacing * (2.0f + row) + 2.0f), CSize2D(estimateWidth, lineSpacing)), + CRect(CVector2D(0.0f, height * (2.0f + row) + 2.0f), CSize2D(estimateWidth, height)), row % 2 ? CColor(1.0f, 1.0f, 1.0f, 0.1f): CColor(0.0f, 0.0f, 0.0f, 0.1f)); } @@ -188,8 +188,8 @@ void CProfileViewer::RenderProfile(CCanvas2D& canvas) CTextRenderer textRenderer; textRenderer.SetCurrentFont(font_name); textRenderer.SetCurrentColor(CColor(1.0f, 1.0f, 1.0f, 1.0f)); - textRenderer.PrintfAt(2.0f, lineSpacing, L"%hs", table->GetTitle().c_str()); - textRenderer.Translate(22.0f, lineSpacing*2.0f); + textRenderer.PrintfAt(2.0f, height, L"%hs", table->GetTitle().c_str()); + textRenderer.Translate(22.0f, height*2.0f); float colX = 0.0f; for (size_t col = 0; col < columns.size(); ++col) @@ -206,7 +206,7 @@ void CProfileViewer::RenderProfile(CCanvas2D& canvas) colX += columns[col].width; } - textRenderer.Translate(0.0f, lineSpacing); + textRenderer.Translate(0.0f, height); // Print rows int currentExpandId = 1; @@ -239,14 +239,14 @@ void CProfileViewer::RenderProfile(CCanvas2D& canvas) rowColX += columns[col].width; } - textRenderer.Translate(0.0f, lineSpacing); + textRenderer.Translate(0.0f, height); } textRenderer.SetCurrentColor(CColor(1.0f, 1.0f, 1.0f, 1.0f)); if (m->path.size() > 1) { - textRenderer.Translate(0.0f, lineSpacing); + textRenderer.Translate(0.0f, height); textRenderer.Put(-15.0f, 0.0f, L"0"); textRenderer.Put(0.0f, 0.0f, L"back to parent"); }