Introduce flag to use typographical line metrics for fonts
For backwards compatibility reasons, font files have multiple different ways to specify vertical metrics (ascent, descent, etc.). For OpenType, the main two are the usWin* and sTypo* metrics in the OS/2 font table. The usWin* metrics are typically used as the clipping bounds of the font (so no character will ever draw outside these bounds). The sTypo* metrics thus make it possible to specify a different set of metrics for use in text layouts which is smaller than the clipping bounds (or bigger), so that you can have fonts where some characters overlap with preceding or subsequent lines. However, GDI (and thus many applications) use usWin* also for line spacing, which lead to the sTypo* metrics being untrustworthy in some fonts and later to the introduction of the USE_TYPO_METRICS in the OS/2 table version 4. The idea of this flag is to tell the font system that the sTypo* metrics can be trusted and should be preferred over the usWin* metrics. But the OpenType specification states that sTypo* metrics should *always* be preferred and modern font systems such as FreeType and DirectWrite will respect this. This in turn has lead to fonts where the USE_TYPO_METRICS flag is untrustworthy instead, i.e. the sTypo* metrics are preferable, but the USE_TYPO_METRICS has accidentally not been set. Qt trusts the USE_TYPO_METRICS flag and uses the usWin* metrics whenever this is unset. Since QFontMetricsF::height() (ascent+descent) in this case includes the line gap metric, a lot of components have been written to use it for size without adding any margins over the text. So changing the default now would break a large amount of components, including the ones in our own Windows style. Most fonts should work correctly, by setting the USE_TYPO_METRICS flag if the typo metrics are intended to be used. For those that do not, we introduce a PreferTypoLineMetrics style strategy. [ChangeLog][QtGui][Fonts] Added QFont::PreferTypoLineMetrics for using the recommended line spacing metrics of the font, even if the font has not explicitly set its USE_TYPO_METRICS flag. Fixes: QTBUG-125585 Change-Id: Ib2f7df404fe719186d78733bda26da712f1ab85a Reviewed-by: Eskil Abrahamsen Blomfeldt <eskil.abrahamsen-blomfeldt@qt.io>
This commit is contained in:
parent
d41ae1a9a8
commit
1bc78f7739
@ -336,7 +336,10 @@ void QCoreTextFontEngine::initializeHeightMetrics() const
|
|||||||
m_descent = QFixed::fromReal(CTFontGetDescent(ctfont));
|
m_descent = QFixed::fromReal(CTFontGetDescent(ctfont));
|
||||||
m_leading = QFixed::fromReal(CTFontGetLeading(ctfont));
|
m_leading = QFixed::fromReal(CTFontGetLeading(ctfont));
|
||||||
|
|
||||||
m_heightMetricsQueried = true;
|
if (preferTypoLineMetrics())
|
||||||
|
QFontEngine::initializeHeightMetrics();
|
||||||
|
else
|
||||||
|
m_heightMetricsQueried = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
QFixed QCoreTextFontEngine::capHeight() const
|
QFixed QCoreTextFontEngine::capHeight() const
|
||||||
|
@ -1470,6 +1470,23 @@ QFont::StyleHint QFont::styleHint() const
|
|||||||
font that matches the largest subset of the input string instead. This will be more
|
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.
|
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.
|
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
|
\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
|
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.
|
looking font that contains the character. The NoFontMerging flag disables this feature.
|
||||||
|
@ -36,19 +36,20 @@ public:
|
|||||||
Q_ENUM(StyleHint)
|
Q_ENUM(StyleHint)
|
||||||
|
|
||||||
enum StyleStrategy {
|
enum StyleStrategy {
|
||||||
PreferDefault = 0x0001,
|
PreferDefault = 0x0001,
|
||||||
PreferBitmap = 0x0002,
|
PreferBitmap = 0x0002,
|
||||||
PreferDevice = 0x0004,
|
PreferDevice = 0x0004,
|
||||||
PreferOutline = 0x0008,
|
PreferOutline = 0x0008,
|
||||||
ForceOutline = 0x0010,
|
ForceOutline = 0x0010,
|
||||||
PreferMatch = 0x0020,
|
PreferMatch = 0x0020,
|
||||||
PreferQuality = 0x0040,
|
PreferQuality = 0x0040,
|
||||||
PreferAntialias = 0x0080,
|
PreferAntialias = 0x0080,
|
||||||
NoAntialias = 0x0100,
|
NoAntialias = 0x0100,
|
||||||
NoSubpixelAntialias = 0x0800,
|
NoSubpixelAntialias = 0x0800,
|
||||||
PreferNoShaping = 0x1000,
|
PreferNoShaping = 0x1000,
|
||||||
ContextFontMerging = 0x2000,
|
ContextFontMerging = 0x2000,
|
||||||
NoFontMerging = 0x8000
|
PreferTypoLineMetrics = 0x4000,
|
||||||
|
NoFontMerging = 0x8000
|
||||||
};
|
};
|
||||||
Q_ENUM(StyleStrategy)
|
Q_ENUM(StyleStrategy)
|
||||||
|
|
||||||
|
@ -430,6 +430,11 @@ void QFontEngine::initializeHeightMetrics() const
|
|||||||
m_heightMetricsQueried = true;
|
m_heightMetricsQueried = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool QFontEngine::preferTypoLineMetrics() const
|
||||||
|
{
|
||||||
|
return (fontDef.styleStrategy & QFont::PreferTypoLineMetrics) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
bool QFontEngine::processOS2Table() const
|
bool QFontEngine::processOS2Table() const
|
||||||
{
|
{
|
||||||
QByteArray os2 = getSfntTable(QFont::Tag("OS/2").value());
|
QByteArray os2 = getSfntTable(QFont::Tag("OS/2").value());
|
||||||
@ -444,7 +449,7 @@ bool QFontEngine::processOS2Table() const
|
|||||||
|
|
||||||
enum { USE_TYPO_METRICS = 0x80 };
|
enum { USE_TYPO_METRICS = 0x80 };
|
||||||
QFixed unitsPerEm = emSquareSize();
|
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.
|
// Some fonts may have invalid OS/2 data. We detect this and bail out.
|
||||||
if (typoAscent == 0 && typoDescent == 0)
|
if (typoAscent == 0 && typoDescent == 0)
|
||||||
return false;
|
return false;
|
||||||
|
@ -148,6 +148,7 @@ public:
|
|||||||
return subPixelPositionFor(QFixedPoint(x, 0)).x;
|
return subPixelPositionFor(QFixedPoint(x, 0)).x;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool preferTypoLineMetrics() const;
|
||||||
bool isColorFont() const { return glyphFormat == Format_ARGB; }
|
bool isColorFont() const { return glyphFormat == Format_ARGB; }
|
||||||
static bool isIgnorableChar(char32_t ucs4)
|
static bool isIgnorableChar(char32_t ucs4)
|
||||||
{
|
{
|
||||||
|
@ -24,8 +24,12 @@ qt_internal_add_test(tst_qfontmetrics
|
|||||||
set_source_files_properties("../../../shared/resources/testfont.ttf"
|
set_source_files_properties("../../../shared/resources/testfont.ttf"
|
||||||
PROPERTIES QT_RESOURCE_ALIAS "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
|
set(testfont_resource_files
|
||||||
"../../../shared/resources/testfont.ttf"
|
"../../../shared/resources/testfont.ttf"
|
||||||
|
"../../../shared/resources/testfont_linemetrics.otf"
|
||||||
"ucs4font.ttf"
|
"ucs4font.ttf"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -36,6 +36,7 @@ private slots:
|
|||||||
void verticalMetrics();
|
void verticalMetrics();
|
||||||
void largeText_data();
|
void largeText_data();
|
||||||
void largeText(); // QTBUG-123339
|
void largeText(); // QTBUG-123339
|
||||||
|
void typoLineMetrics();
|
||||||
};
|
};
|
||||||
|
|
||||||
void tst_QFontMetrics::same()
|
void tst_QFontMetrics::same()
|
||||||
@ -410,5 +411,48 @@ void tst_QFontMetrics::largeText()
|
|||||||
QVERIFY(boundingRect.isValid());
|
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)
|
QTEST_MAIN(tst_QFontMetrics)
|
||||||
#include "tst_qfontmetrics.moc"
|
#include "tst_qfontmetrics.moc"
|
||||||
|
BIN
tests/auto/shared/resources/testfont_linemetrics.otf
Normal file
BIN
tests/auto/shared/resources/testfont_linemetrics.otf
Normal file
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user