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
This commit is contained in:
trompetin17 2025-05-28 22:37:37 -05:00
parent 638d4d28d2
commit dfb4598186
No known key found for this signature in database
GPG key ID: 6C585FA3FC5DB179
11 changed files with 89 additions and 77 deletions

View file

@ -28,6 +28,7 @@
#include <numeric>
#include <string>
#include <vector>
#include <cstdlib>
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;

View file

@ -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 flatsuch as H or Ias 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;

View file

@ -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();
}

View file

@ -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;

View file

@ -33,6 +33,8 @@
#include "ps/Hotkey.h"
#include <sstream>
#include <maths/Size2D.h>
#include <cstdlib>
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.

View file

@ -27,6 +27,7 @@
#include <algorithm>
#include <array>
#include <cstdlib>
// 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;

View file

@ -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;

View file

@ -42,6 +42,7 @@
#include "scriptinterface/JSON.h"
#include <algorithm>
#include <cmath>
#include <string_view>
#include <vector>
#include <wctype.h>
@ -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<size_t>(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<float>(m_FontHeight) - 4.0f},
CVector2D{m_Width, m_Height - static_cast<float>(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<float>(m_FontOffset) - static_cast<float>(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<float>(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<std::mutex> lock(m_Mutex); // needed for safe access to m_deqMsgHistory
const int linesShown = static_cast<int>(m_Height / m_FontHeight) - 4;
const int linesShown = static_cast<int>(std::ceil(m_Height / m_FontHeight)) - 4;
m_MsgHistPos = Clamp(static_cast<int>(m_MsgHistory.size()) - linesShown, 1, static_cast<int>(m_MsgHistory.size()));
}
else

View file

@ -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;

View file

@ -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);

View file

@ -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<float>(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");
}