diff --git a/src/gui/text/coretext/qfontengine_coretext.mm b/src/gui/text/coretext/qfontengine_coretext.mm index 1050c03d75b..87ed95f3ace 100644 --- a/src/gui/text/coretext/qfontengine_coretext.mm +++ b/src/gui/text/coretext/qfontengine_coretext.mm @@ -336,7 +336,10 @@ void QCoreTextFontEngine::initializeHeightMetrics() const m_descent = QFixed::fromReal(CTFontGetDescent(ctfont)); m_leading = QFixed::fromReal(CTFontGetLeading(ctfont)); - m_heightMetricsQueried = true; + if (preferTypoLineMetrics()) + QFontEngine::initializeHeightMetrics(); + else + m_heightMetricsQueried = true; } QFixed QCoreTextFontEngine::capHeight() const diff --git a/src/gui/text/qfont.cpp b/src/gui/text/qfont.cpp index f3a35a4269d..c8881a9bf8b 100644 --- a/src/gui/text/qfont.cpp +++ b/src/gui/text/qfont.cpp @@ -1470,6 +1470,23 @@ QFont::StyleHint QFont::styleHint() const font that matches the largest subset of the input string instead. This will be more expensive for strings where missing glyphs occur, but may give more consistent results. If \c NoFontMerging is set, then \c ContextFontMerging will have no effect. + \value [since 6.8] PreferTypoLineMetrics For compatibility reasons, OpenType fonts contain + two competing sets of the vertical line metrics that provide the + \l{QFontMetricsF::ascent()}{ascent}, \l{QFontMetricsF::descent()}{descent} and + \l{QFontMetricsF::leading()}{leading} of the font. These are often referred to as the + \l{https://learn.microsoft.com/en-us/typography/opentype/spec/os2#uswinascent}{win} + (Windows) metrics and the + \l{https://learn.microsoft.com/en-us/typography/opentype/spec/os2#sta}{typo} + (typographical) metrics. While the specification recommends using the \c typo metrics for + line spacing, many applications prefer the \c win metrics unless the \c{USE_TYPO_METRICS} + flag is set in the + \l{https://learn.microsoft.com/en-us/typography/opentype/spec/os2#fsselection}{fsSelection} + field of the font. For backwards-compatibility reasons, this is also the case for Qt + applications. This is not an issue for fonts that set the \c{USE_TYPO_METRICS} flag to + indicate that the \c{typo} metrics are valid, nor for fonts where the \c{win} metrics + and \c{typo} metrics match up. However, for certain fonts the \c{win} metrics may be + larger than the preferable line spacing and the \c{USE_TYPO_METRICS} flag may be unset + by mistake. For such fonts, setting \c{PreferTypoLineMetrics} may give superior results. \value NoFontMerging If the font selected for a certain writing system does not contain a character requested to draw, then Qt automatically chooses a similar looking font that contains the character. The NoFontMerging flag disables this feature. diff --git a/src/gui/text/qfont.h b/src/gui/text/qfont.h index 66a5f7c155a..0b41e31bdb4 100644 --- a/src/gui/text/qfont.h +++ b/src/gui/text/qfont.h @@ -36,19 +36,20 @@ public: Q_ENUM(StyleHint) enum StyleStrategy { - PreferDefault = 0x0001, - PreferBitmap = 0x0002, - PreferDevice = 0x0004, - PreferOutline = 0x0008, - ForceOutline = 0x0010, - PreferMatch = 0x0020, - PreferQuality = 0x0040, - PreferAntialias = 0x0080, - NoAntialias = 0x0100, - NoSubpixelAntialias = 0x0800, - PreferNoShaping = 0x1000, - ContextFontMerging = 0x2000, - NoFontMerging = 0x8000 + PreferDefault = 0x0001, + PreferBitmap = 0x0002, + PreferDevice = 0x0004, + PreferOutline = 0x0008, + ForceOutline = 0x0010, + PreferMatch = 0x0020, + PreferQuality = 0x0040, + PreferAntialias = 0x0080, + NoAntialias = 0x0100, + NoSubpixelAntialias = 0x0800, + PreferNoShaping = 0x1000, + ContextFontMerging = 0x2000, + PreferTypoLineMetrics = 0x4000, + NoFontMerging = 0x8000 }; Q_ENUM(StyleStrategy) diff --git a/src/gui/text/qfontengine.cpp b/src/gui/text/qfontengine.cpp index 4e78aaac2e4..ee74c7f8fb8 100644 --- a/src/gui/text/qfontengine.cpp +++ b/src/gui/text/qfontengine.cpp @@ -430,6 +430,11 @@ void QFontEngine::initializeHeightMetrics() const m_heightMetricsQueried = true; } +bool QFontEngine::preferTypoLineMetrics() const +{ + return (fontDef.styleStrategy & QFont::PreferTypoLineMetrics) != 0; +} + bool QFontEngine::processOS2Table() const { QByteArray os2 = getSfntTable(QFont::Tag("OS/2").value()); @@ -444,7 +449,7 @@ bool QFontEngine::processOS2Table() const enum { USE_TYPO_METRICS = 0x80 }; QFixed unitsPerEm = emSquareSize(); - if (fsSelection & USE_TYPO_METRICS) { + if (preferTypoLineMetrics() || fsSelection & USE_TYPO_METRICS) { // Some fonts may have invalid OS/2 data. We detect this and bail out. if (typoAscent == 0 && typoDescent == 0) return false; diff --git a/src/gui/text/qfontengine_p.h b/src/gui/text/qfontengine_p.h index a0e08013545..807f02fdc7d 100644 --- a/src/gui/text/qfontengine_p.h +++ b/src/gui/text/qfontengine_p.h @@ -148,6 +148,7 @@ public: return subPixelPositionFor(QFixedPoint(x, 0)).x; } + bool preferTypoLineMetrics() const; bool isColorFont() const { return glyphFormat == Format_ARGB; } static bool isIgnorableChar(char32_t ucs4) { diff --git a/tests/auto/gui/text/qfontmetrics/CMakeLists.txt b/tests/auto/gui/text/qfontmetrics/CMakeLists.txt index d014d27d46c..ee2f76ef76a 100644 --- a/tests/auto/gui/text/qfontmetrics/CMakeLists.txt +++ b/tests/auto/gui/text/qfontmetrics/CMakeLists.txt @@ -24,8 +24,12 @@ qt_internal_add_test(tst_qfontmetrics set_source_files_properties("../../../shared/resources/testfont.ttf" PROPERTIES QT_RESOURCE_ALIAS "testfont.ttf" ) +set_source_files_properties("../../../shared/resources/testfont_linemetrics.otf" + PROPERTIES QT_RESOURCE_ALIAS "testfont_linemetrics.otf" +) set(testfont_resource_files "../../../shared/resources/testfont.ttf" + "../../../shared/resources/testfont_linemetrics.otf" "ucs4font.ttf" ) diff --git a/tests/auto/gui/text/qfontmetrics/tst_qfontmetrics.cpp b/tests/auto/gui/text/qfontmetrics/tst_qfontmetrics.cpp index 9471c1d93f8..bad33ab0a47 100644 --- a/tests/auto/gui/text/qfontmetrics/tst_qfontmetrics.cpp +++ b/tests/auto/gui/text/qfontmetrics/tst_qfontmetrics.cpp @@ -36,6 +36,7 @@ private slots: void verticalMetrics(); void largeText_data(); void largeText(); // QTBUG-123339 + void typoLineMetrics(); }; void tst_QFontMetrics::same() @@ -410,5 +411,48 @@ void tst_QFontMetrics::largeText() QVERIFY(boundingRect.isValid()); } +void tst_QFontMetrics::typoLineMetrics() +{ + QString testFont = QFINDTESTDATA("fonts/testfont_linemetrics.otf"); + QVERIFY(!testFont.isEmpty()); + + int id = QFontDatabase::addApplicationFont(testFont); + QVERIFY(id >= 0); + + { + auto cleanup = qScopeGuard([&id] { + if (id >= 0) + QFontDatabase::removeApplicationFont(id); + }); + + QImage img(100, 100, QImage::Format_ARGB32); + img.setDevicePixelRatio(1.0); + QFont font(QFontDatabase::applicationFontFamilies(id).at(0), &img); + font.setPixelSize(18); + + const qreal unitsPerEm = 1000.0; + + QFontMetrics defaultFm(font); + const int defaultAscent = defaultFm.ascent(); + const int defaultDescent = defaultFm.descent(); + const int defaultLeading = defaultFm.leading(); + + QCOMPARE(defaultAscent, qRound(1234.0 / unitsPerEm * font.pixelSize())); + QCOMPARE(defaultDescent, qRound(5678.0 / unitsPerEm * font.pixelSize())); + QCOMPARE(defaultLeading, 0.0); + + font.setStyleStrategy(QFont::PreferTypoLineMetrics); + const QFontMetrics typoFm(font); + + const int typoAscent = typoFm.ascent(); + const int typoDescent = typoFm.descent(); + const int typoLeading = typoFm.leading(); + + QCOMPARE(typoAscent, qRound(2000.0 / unitsPerEm * font.pixelSize())); + QCOMPARE(typoDescent, qRound(3000.0 / unitsPerEm * font.pixelSize())); + QCOMPARE(typoLeading, qRound(1000.0 / unitsPerEm * font.pixelSize())); + } +} + QTEST_MAIN(tst_QFontMetrics) #include "tst_qfontmetrics.moc" diff --git a/tests/auto/shared/resources/testfont_linemetrics.otf b/tests/auto/shared/resources/testfont_linemetrics.otf new file mode 100644 index 00000000000..11368f7d9e6 Binary files /dev/null and b/tests/auto/shared/resources/testfont_linemetrics.otf differ