From 6d8eff51199d34005f5272e76573959d739d3114 Mon Sep 17 00:00:00 2001 From: Alexander Pavlov Date: Wed, 19 Aug 2026 18:07:24 +0300 Subject: [PATCH] add underline/strikethrough metrics to font engine --- framework/draw/fontmetrics.cpp | 15 +++++++ framework/draw/fontmetrics.h | 4 ++ framework/draw/ifontprovider.h | 4 ++ framework/draw/internal/fontfacedu.cpp | 10 +++++ framework/draw/internal/fontfacedu.h | 3 ++ framework/draw/internal/fontfaceft.cpp | 12 +++++ framework/draw/internal/fontfaceft.h | 3 ++ framework/draw/internal/fontfacext.cpp | 14 ++++++ framework/draw/internal/fontfacext.h | 5 +++ framework/draw/internal/fontprovider.cpp | 15 +++++++ framework/draw/internal/fontprovider.h | 4 ++ framework/draw/internal/fontsengine.cpp | 23 ++++++++++ framework/draw/internal/fontsengine.h | 4 ++ framework/draw/internal/ifontface.h | 3 ++ framework/draw/internal/ifontsengine.h | 4 ++ framework/draw/internal/qfontprovider.cpp | 15 +++++++ framework/draw/internal/qfontprovider.h | 4 ++ .../draw/tests/fontsprovider_qt_tests.cpp | 45 +++++++++++++++++++ framework/draw/types/fontstypes.h | 2 + 19 files changed, 189 insertions(+) diff --git a/framework/draw/fontmetrics.cpp b/framework/draw/fontmetrics.cpp index 17c7b0e87a..54dbffd874 100644 --- a/framework/draw/fontmetrics.cpp +++ b/framework/draw/fontmetrics.cpp @@ -59,6 +59,21 @@ double FontMetrics::descent() const return fontProvider()->descent(m_font); } +double FontMetrics::underlinePos() const +{ + return fontProvider()->underlinePos(m_font); +} + +double FontMetrics::lineWidth() const +{ + return fontProvider()->lineWidth(m_font); +} + +double FontMetrics::strikeOutPos() const +{ + return fontProvider()->strikeOutPos(m_font); +} + double FontMetrics::width(const String& string) const { return horizontalAdvance(string); diff --git a/framework/draw/fontmetrics.h b/framework/draw/fontmetrics.h index c822548fc4..d1da9098a0 100644 --- a/framework/draw/fontmetrics.h +++ b/framework/draw/fontmetrics.h @@ -45,6 +45,10 @@ class FontMetrics double ascent() const; double descent() const; + double underlinePos() const; + double lineWidth() const; + double strikeOutPos() const; + double width(const String& string) const; double width(const Char& ch) const; diff --git a/framework/draw/ifontprovider.h b/framework/draw/ifontprovider.h index 136c57835d..e686083370 100644 --- a/framework/draw/ifontprovider.h +++ b/framework/draw/ifontprovider.h @@ -47,6 +47,10 @@ class IFontProvider : MODULE_GLOBAL_INTERFACE virtual double ascent(const Font& f) const = 0; virtual double descent(const Font& f) const = 0; + virtual double underlinePos(const Font& f) const = 0; + virtual double lineWidth(const Font& f) const = 0; + virtual double strikeOutPos(const Font& f) const = 0; + virtual bool inFont(const Font& f, char32_t ucs4) const = 0; // Text diff --git a/framework/draw/internal/fontfacedu.cpp b/framework/draw/internal/fontfacedu.cpp index b1291059b6..3ee4caf687 100644 --- a/framework/draw/internal/fontfacedu.cpp +++ b/framework/draw/internal/fontfacedu.cpp @@ -93,6 +93,16 @@ f26dot6_t FontFaceDU::capHeight() const return m_origin->capHeight(); } +f26dot6_t FontFaceDU::underlinePos() const +{ + return m_origin->underlinePos(); +} + +f26dot6_t FontFaceDU::lineWidth() const +{ + return m_origin->lineWidth(); +} + std::vector FontFaceDU::glyphs(const char32_t* text, int text_length) const { std::vector glyphs = m_origin->glyphs(text, text_length); diff --git a/framework/draw/internal/fontfacedu.h b/framework/draw/internal/fontfacedu.h index ea6d8b7b66..7c889bd6a9 100644 --- a/framework/draw/internal/fontfacedu.h +++ b/framework/draw/internal/fontfacedu.h @@ -41,6 +41,9 @@ class FontFaceDU : public IFontFace f26dot6_t xHeight() const override; f26dot6_t capHeight() const override; + f26dot6_t underlinePos() const override; + f26dot6_t lineWidth() const override; + std::vector glyphs(const char32_t* text, int text_length) const override; glyph_idx_t glyphIndex(char32_t ucs4) const override; glyph_idx_t glyphIndex(const std::string& glyphName) const override; diff --git a/framework/draw/internal/fontfaceft.cpp b/framework/draw/internal/fontfaceft.cpp index bd6d283136..2407c54e6b 100644 --- a/framework/draw/internal/fontfaceft.cpp +++ b/framework/draw/internal/fontfaceft.cpp @@ -570,6 +570,18 @@ f26dot6_t FontFaceFT::capHeight() const return gm->bbox.height(); } +f26dot6_t FontFaceFT::underlinePos() const +{ + f26dot6_t thickness = FT_MulFix(m_data->face->underline_thickness, m_data->face->size->metrics.y_scale); + f26dot6_t centerPos = -FT_MulFix(m_data->face->underline_position, m_data->face->size->metrics.y_scale); + return centerPos - thickness / 2; +} + +f26dot6_t FontFaceFT::lineWidth() const +{ + return FT_MulFix(m_data->face->underline_thickness, m_data->face->size->metrics.y_scale); +} + GlyphMetrics* FontFaceFT::glyphMetrics(glyph_idx_t idx) const { if (m_data->glyphsMetrics.find(idx) != m_data->glyphsMetrics.end()) { diff --git a/framework/draw/internal/fontfaceft.h b/framework/draw/internal/fontfaceft.h index 45c2124742..5e184b2478 100644 --- a/framework/draw/internal/fontfaceft.h +++ b/framework/draw/internal/fontfaceft.h @@ -45,6 +45,9 @@ class FontFaceFT : public IFontFace f26dot6_t xHeight() const override; f26dot6_t capHeight() const override; + f26dot6_t underlinePos() const override; + f26dot6_t lineWidth() const override; + std::vector glyphs(const char32_t* text, int text_length) const override; glyph_idx_t glyphIndex(char32_t ucs4) const override; glyph_idx_t glyphIndex(const std::string& glyphName) const override; diff --git a/framework/draw/internal/fontfacext.cpp b/framework/draw/internal/fontfacext.cpp index 6535b6f44e..a56dc49b7c 100644 --- a/framework/draw/internal/fontfacext.cpp +++ b/framework/draw/internal/fontfacext.cpp @@ -107,6 +107,10 @@ bool FontFaceXT::load(const FaceKey& key, const FontData& fontData, bool isSymbo m_xHeight = std::stol(valStr); } else if (name == "capHeight") { m_capHeight = std::stol(valStr); + } else if (name == "underlinePos") { + m_underlinePos = std::stol(valStr); + } else if (name == "lineWidth") { + m_lineWidth = std::stol(valStr); } else { LOGW() << "unknown param: " << name; } @@ -277,6 +281,16 @@ f26dot6_t FontFaceXT::capHeight() const return m_capHeight; } +f26dot6_t FontFaceXT::underlinePos() const +{ + return m_underlinePos; +} + +f26dot6_t FontFaceXT::lineWidth() const +{ + return m_lineWidth; +} + void FontFaceXT::applyLigatures(std::vector& glyphs, const Ligatures& ls) { for (const Ligature& l : ls) { diff --git a/framework/draw/internal/fontfacext.h b/framework/draw/internal/fontfacext.h index 19837a6d5f..d74a08b26c 100644 --- a/framework/draw/internal/fontfacext.h +++ b/framework/draw/internal/fontfacext.h @@ -90,6 +90,9 @@ class FontFaceXT : public IFontFace f26dot6_t xHeight() const override; f26dot6_t capHeight() const override; + f26dot6_t underlinePos() const override; + f26dot6_t lineWidth() const override; + std::vector glyphs(const char32_t* text, int text_length) const override; glyph_idx_t glyphIndex(char32_t ucs4) const override; glyph_idx_t glyphIndex(const std::string& glyphName) const override; @@ -121,6 +124,8 @@ class FontFaceXT : public IFontFace f26dot6_t m_descent = -1; f26dot6_t m_xHeight = -1; f26dot6_t m_capHeight = -1; + f26dot6_t m_underlinePos = -1; + f26dot6_t m_lineWidth = -1; Ligatures m_ligatures; Kernings m_kernings; diff --git a/framework/draw/internal/fontprovider.cpp b/framework/draw/internal/fontprovider.cpp index 4589100097..edccccee61 100644 --- a/framework/draw/internal/fontprovider.cpp +++ b/framework/draw/internal/fontprovider.cpp @@ -63,6 +63,21 @@ double FontProvider::descent(const muse::draw::Font& f) const return fontsEngine()->descent(f); } +double FontProvider::underlinePos(const muse::draw::Font& f) const +{ + return fontsEngine()->underlinePos(f); +} + +double FontProvider::lineWidth(const muse::draw::Font& f) const +{ + return fontsEngine()->lineWidth(f); +} + +double FontProvider::strikeOutPos(const muse::draw::Font& f) const +{ + return fontsEngine()->strikeOutPos(f); +} + bool FontProvider::inFont(const muse::draw::Font& f, char32_t ucs4) const { return fontsEngine()->inFont(f, ucs4); diff --git a/framework/draw/internal/fontprovider.h b/framework/draw/internal/fontprovider.h index 57eb23e646..245cd49f02 100644 --- a/framework/draw/internal/fontprovider.h +++ b/framework/draw/internal/fontprovider.h @@ -46,6 +46,10 @@ class FontProvider : public IFontProvider, public Contextable double ascent(const Font& f) const override; double descent(const Font& f) const override; + double underlinePos(const Font& f) const override; + double lineWidth(const Font& f) const override; + double strikeOutPos(const Font& f) const override; + bool inFont(const Font& f, char32_t ucs4) const override; // Text diff --git a/framework/draw/internal/fontsengine.cpp b/framework/draw/internal/fontsengine.cpp index 7305e4ff0a..d17b679fa6 100644 --- a/framework/draw/internal/fontsengine.cpp +++ b/framework/draw/internal/fontsengine.cpp @@ -204,6 +204,29 @@ double FontsEngine::descent(const Font& f) const return from_f26d6(rf->face->descent()) * rf->pixelScale(); } +double FontsEngine::underlinePos(const Font& f) const +{ + RequireFace* rf = fontFace(f); + IF_ASSERT_FAILED(rf && rf->face) { + return 0.0; + } + return from_f26d6(rf->face->underlinePos()) * rf->pixelScale(); +} + +double FontsEngine::lineWidth(const Font& f) const +{ + RequireFace* rf = fontFace(f); + IF_ASSERT_FAILED(rf && rf->face) { + return 1.0; + } + return from_f26d6(rf->face->lineWidth()) * rf->pixelScale(); +} + +double FontsEngine::strikeOutPos(const Font& f) const +{ + return ascent(f) / 3.0; +} + bool FontsEngine::inFont(const Font& f, char32_t ucs4) const { RequireFace* rf = fontFace(f); diff --git a/framework/draw/internal/fontsengine.h b/framework/draw/internal/fontsengine.h index 8b4f9df3fd..9175921fe7 100644 --- a/framework/draw/internal/fontsengine.h +++ b/framework/draw/internal/fontsengine.h @@ -53,6 +53,10 @@ class FontsEngine : public IFontsEngine, public Contextable double ascent(const Font& f) const override; double descent(const Font& f) const override; + double underlinePos(const Font& f) const override; + double lineWidth(const Font& f) const override; + double strikeOutPos(const Font& f) const override; + bool inFont(const Font& f, char32_t ucs4) const override; double horizontalAdvance(const Font& f, const char32_t& ch) const override; diff --git a/framework/draw/internal/ifontface.h b/framework/draw/internal/ifontface.h index 3dbeae0e07..749a56ddf4 100644 --- a/framework/draw/internal/ifontface.h +++ b/framework/draw/internal/ifontface.h @@ -54,6 +54,9 @@ class IFontFace virtual f26dot6_t xHeight() const = 0; virtual f26dot6_t capHeight() const = 0; + virtual f26dot6_t underlinePos() const = 0; + virtual f26dot6_t lineWidth() const = 0; + virtual std::vector glyphs(const char32_t* text, int text_length) const = 0; virtual glyph_idx_t glyphIndex(char32_t ucs4) const = 0; virtual glyph_idx_t glyphIndex(const std::string& glyphName) const = 0; diff --git a/framework/draw/internal/ifontsengine.h b/framework/draw/internal/ifontsengine.h index cf9a44d4b7..e5ec3c4a70 100644 --- a/framework/draw/internal/ifontsengine.h +++ b/framework/draw/internal/ifontsengine.h @@ -46,6 +46,10 @@ class IFontsEngine : MODULE_GLOBAL_INTERFACE virtual double ascent(const Font& f) const = 0; virtual double descent(const Font& f) const = 0; + virtual double underlinePos(const Font& f) const = 0; + virtual double lineWidth(const Font& f) const = 0; + virtual double strikeOutPos(const Font& f) const = 0; + virtual bool inFont(const Font& f, char32_t ucs4) const = 0; virtual double horizontalAdvance(const Font& f, const char32_t& ch) const = 0; diff --git a/framework/draw/internal/qfontprovider.cpp b/framework/draw/internal/qfontprovider.cpp index 2f7938cb67..b2ce094c74 100644 --- a/framework/draw/internal/qfontprovider.cpp +++ b/framework/draw/internal/qfontprovider.cpp @@ -91,6 +91,21 @@ double QFontProvider::descent(const Font& f) const return QFontMetricsF(f.toQFont(), &device).descent(); } +double QFontProvider::underlinePos(const Font& f) const +{ + return QFontMetricsF(f.toQFont(), &device).underlinePos(); +} + +double QFontProvider::lineWidth(const Font& f) const +{ + return QFontMetricsF(f.toQFont(), &device).lineWidth(); +} + +double QFontProvider::strikeOutPos(const Font& f) const +{ + return QFontMetricsF(f.toQFont(), &device).strikeOutPos(); +} + bool QFontProvider::inFont(const Font& f, char32_t ucs4) const { // NOTE: QFontMetricsF::inFontUcs4 is unreliable for our use case because it uses Qt's fallback diff --git a/framework/draw/internal/qfontprovider.h b/framework/draw/internal/qfontprovider.h index dd2de12499..a8ea28d580 100644 --- a/framework/draw/internal/qfontprovider.h +++ b/framework/draw/internal/qfontprovider.h @@ -41,6 +41,10 @@ class QFontProvider : public IFontProvider double ascent(const Font& f) const override; double descent(const Font& f) const override; + double underlinePos(const Font& f) const override; + double lineWidth(const Font& f) const override; + double strikeOutPos(const Font& f) const override; + bool inFont(const Font& f, char32_t ucs4) const override; // Text diff --git a/framework/draw/tests/fontsprovider_qt_tests.cpp b/framework/draw/tests/fontsprovider_qt_tests.cpp index 05a99ccbc5..409588006f 100644 --- a/framework/draw/tests/fontsprovider_qt_tests.cpp +++ b/framework/draw/tests/fontsprovider_qt_tests.cpp @@ -260,6 +260,51 @@ TEST_F(Draw_FontsProviderQtTests, descent) } } +TEST_F(Draw_FontsProviderQtTests, underlinePos) +{ + Env env; + Font f(u"Edwin", Font::Type::Text); + + for (double pointSize : TEST_POINTSIZES) { + f.setPointSizeF(pointSize); + + double qVal = env.qProvider.underlinePos(f); + double xVal = env.xProvider.underlinePos(f); + + EXPECT_TRUE(ValuesMatch(f.pointSizeF(), qVal, xVal, 0.0)); + } +} + +TEST_F(Draw_FontsProviderQtTests, lineWidth) +{ + Env env; + Font f(u"Edwin", Font::Type::Text); + + for (double pointSize : TEST_POINTSIZES) { + f.setPointSizeF(pointSize); + + double qVal = env.qProvider.lineWidth(f); + double xVal = env.xProvider.lineWidth(f); + + EXPECT_TRUE(ValuesMatch(f.pointSizeF(), qVal, xVal, 0.0)); + } +} + +TEST_F(Draw_FontsProviderQtTests, strikeOutPos) +{ + Env env; + Font f(u"Edwin", Font::Type::Text); + + for (double pointSize : TEST_POINTSIZES) { + f.setPointSizeF(pointSize); + + double qVal = env.qProvider.strikeOutPos(f); + double xVal = env.xProvider.strikeOutPos(f); + + EXPECT_TRUE(ValuesMatch(f.pointSizeF(), qVal, xVal, 0.0)); + } +} + TEST_F(Draw_FontsProviderQtTests, horizontalAdvance_Char) { Env env; diff --git a/framework/draw/types/fontstypes.h b/framework/draw/types/fontstypes.h index cdc078aaae..9c6a5c66bb 100644 --- a/framework/draw/types/fontstypes.h +++ b/framework/draw/types/fontstypes.h @@ -150,6 +150,8 @@ struct FontParams { bool bold = false; bool italic = false; float pointSize = 0.0f; + bool underline = false; + bool strike = false; }; }