summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorEskil Abrahamsen Blomfeldt <[email protected]>2024-07-23 13:25:35 +0200
committerEskil Abrahamsen Blomfeldt <[email protected]>2024-08-28 01:49:12 +0200
commitcc128d802c6d9c87a1e00a8a88d5e6590a7195f4 (patch)
tree070a766303bff0f805e61be5845987d6b4eb132c
parentbfe8ac4ebff7a1e8114068f2f46e73c588735690 (diff)
Add API to get variable axis information from font
This adds QFontInfo::variableAxes() which can be used to inspect a variable font and see which axes it defines. It is implemented on DirectWrite, CoreText and FreeType. On FreeType, there is a bug where getting the information about variable axes on a font will cause the FT_Face to be unusable. Unfortunately this happens for Cantarell-VF which is the default font on some platforms. To work around this, we create a temporary duplicate of the FT_Face for multiple masters fonts on older FreeType versions when we fetch the information about the axes so that we can throw away the broken FT_Face after. [ChangeLog][QtGui][Fonts] Added API to get variable axis information for a font. Change-Id: Ia16e0a5f6df5744dc47e7f16c074391afde1f126 Reviewed-by: Volker Hilsheimer <[email protected]>
-rw-r--r--src/gui/CMakeLists.txt1
-rw-r--r--src/gui/text/coretext/qfontengine_coretext.mm49
-rw-r--r--src/gui/text/coretext/qfontengine_coretext_p.h3
-rw-r--r--src/gui/text/freetype/qfontengine_ft.cpp116
-rw-r--r--src/gui/text/freetype/qfontengine_ft_p.h8
-rw-r--r--src/gui/text/qfont.cpp15
-rw-r--r--src/gui/text/qfontengine.cpp9
-rw-r--r--src/gui/text/qfontengine_p.h5
-rw-r--r--src/gui/text/qfontinfo.h3
-rw-r--r--src/gui/text/qfontvariableaxis.cpp226
-rw-r--r--src/gui/text/qfontvariableaxis.h67
-rw-r--r--src/gui/text/windows/qwindowsfontenginedirectwrite.cpp66
-rw-r--r--src/gui/text/windows/qwindowsfontenginedirectwrite_p.h3
-rw-r--r--tests/auto/gui/text/qfont/CMakeLists.txt5
-rw-r--r--tests/auto/gui/text/qfont/tst_qfont.cpp43
15 files changed, 595 insertions, 24 deletions
diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt
index 3b3dbd13218..6feb2bda474 100644
--- a/src/gui/CMakeLists.txt
+++ b/src/gui/CMakeLists.txt
@@ -238,6 +238,7 @@ qt_internal_add_module(Gui
text/qfontinfo.h
text/qfontmetrics.cpp text/qfontmetrics.h
text/qfontsubset.cpp text/qfontsubset_p.h
+ text/qfontvariableaxis.cpp text/qfontvariableaxis.h
text/qfragmentmap.cpp text/qfragmentmap_p.h
text/qglyphrun.cpp text/qglyphrun.h text/qglyphrun_p.h
text/qinputcontrol.cpp text/qinputcontrol_p.h
diff --git a/src/gui/text/coretext/qfontengine_coretext.mm b/src/gui/text/coretext/qfontengine_coretext.mm
index 87ed95f3ace..69be80f9886 100644
--- a/src/gui/text/coretext/qfontengine_coretext.mm
+++ b/src/gui/text/coretext/qfontengine_coretext.mm
@@ -254,6 +254,50 @@ void QCoreTextFontEngine::init()
cache_cost = (CTFontGetAscent(ctfont) + CTFontGetDescent(ctfont)) * avgCharWidth.toInt() * 2000;
kerningPairsLoaded = false;
+
+ if (QCFType<CFArrayRef> variationAxes = CTFontCopyVariationAxes(ctfont)) {
+ CFIndex count = CFArrayGetCount(variationAxes);
+ for (CFIndex i = 0; i < count; ++i) {
+ CFDictionaryRef variationAxis = CFDictionaryRef(CFArrayGetValueAtIndex(variationAxes, i));
+
+ QFontVariableAxis fontVariableAxis;
+ if (CFNumberRef tagRef = (CFNumberRef) CFDictionaryGetValue(variationAxis,
+ kCTFontVariationAxisIdentifierKey)) {
+ quint32 tag;
+ CFNumberGetValue(tagRef, kCFNumberIntType, &tag);
+ if (auto maybeTag = QFont::Tag::fromValue(tag))
+ fontVariableAxis.setTag(*maybeTag);
+ }
+
+ if (CFNumberRef minimumValueRef = (CFNumberRef) CFDictionaryGetValue(variationAxis,
+ kCTFontVariationAxisMinimumValueKey)) {
+ float minimumValue;
+ CFNumberGetValue(minimumValueRef, kCFNumberFloatType, &minimumValue);
+ fontVariableAxis.setMinimumValue(minimumValue);
+ }
+
+ if (CFNumberRef maximumValueRef = (CFNumberRef) CFDictionaryGetValue(variationAxis,
+ kCTFontVariationAxisMaximumValueKey)) {
+ float maximumValue;
+ CFNumberGetValue(maximumValueRef, kCFNumberFloatType, &maximumValue);
+ fontVariableAxis.setMaximumValue(maximumValue);
+ }
+
+ if (CFNumberRef defaultValueRef = (CFNumberRef) CFDictionaryGetValue(variationAxis,
+ kCTFontVariationAxisDefaultValueKey)) {
+ float defaultValue;
+ CFNumberGetValue(defaultValueRef, kCFNumberFloatType, &defaultValue);
+ fontVariableAxis.setDefaultValue(defaultValue);
+ }
+
+ if (CFStringRef nameRef = (CFStringRef) CFDictionaryGetValue(variationAxis,
+ kCTFontVariationAxisNameKey)) {
+ fontVariableAxis.setName(QString::fromCFString(nameRef));
+ }
+
+ variableAxisList.append(fontVariableAxis);
+ }
+ }
}
glyph_t QCoreTextFontEngine::glyphIndex(uint ucs4) const
@@ -1000,4 +1044,9 @@ void QCoreTextFontEngine::doKerning(QGlyphLayout *g, ShaperFlags flags) const
QFontEngine::doKerning(g, flags);
}
+QList<QFontVariableAxis> QCoreTextFontEngine::variableAxes() const
+{
+ return variableAxisList;
+}
+
QT_END_NAMESPACE
diff --git a/src/gui/text/coretext/qfontengine_coretext_p.h b/src/gui/text/coretext/qfontengine_coretext_p.h
index 2f388c32bcf..1b8a7e0d2a3 100644
--- a/src/gui/text/coretext/qfontengine_coretext_p.h
+++ b/src/gui/text/coretext/qfontengine_coretext_p.h
@@ -82,6 +82,8 @@ public:
QFontEngine::Properties properties() const override;
+ QList<QFontVariableAxis> variableAxes() const override;
+
enum FontSmoothing { Disabled, Subpixel, Grayscale };
Q_ENUM(FontSmoothing);
@@ -112,6 +114,7 @@ protected:
QFixed underlinePos;
QFontEngine::FaceId face_id;
mutable bool kerningPairsLoaded;
+ QList<QFontVariableAxis> variableAxisList;
};
CGAffineTransform Q_GUI_EXPORT qt_transform_from_fontdef(const QFontDef &fontDef);
diff --git a/src/gui/text/freetype/qfontengine_ft.cpp b/src/gui/text/freetype/qfontengine_ft.cpp
index f71580a862f..b9a8a8bbb59 100644
--- a/src/gui/text/freetype/qfontengine_ft.cpp
+++ b/src/gui/text/freetype/qfontengine_ft.cpp
@@ -245,6 +245,7 @@ QFreetypeFace *QFreetypeFace::getFace(const QFontEngine::FaceId &face_id,
const auto deleter = [](QFreetypeFace *f) { delete f; };
std::unique_ptr<QFreetypeFace, decltype(deleter)> newFreetype(new QFreetypeFace, deleter);
FT_Face face;
+ FT_Face tmpFace;
if (!face_id.filename.isEmpty()) {
QString fileName = QFile::decodeName(face_id.filename);
if (const char *prefix = ":qmemoryfonts/"; face_id.filename.startsWith(prefix)) {
@@ -265,12 +266,94 @@ QFreetypeFace *QFreetypeFace::getFace(const QFontEngine::FaceId &face_id,
} else {
newFreetype->fontData = fontData;
}
+
+ FT_Int major, minor, patch;
+ FT_Library_Version(qt_getFreetype(), &major, &minor, &patch);
+ const bool goodVersion = major > 2 || (major == 2 && minor > 13) || (major == 2 && minor == 13 && patch > 2);
+
if (!newFreetype->fontData.isEmpty()) {
- if (FT_New_Memory_Face(freetypeData->library, (const FT_Byte *)newFreetype->fontData.constData(), newFreetype->fontData.size(), face_id.index, &face)) {
+ if (FT_New_Memory_Face(freetypeData->library,
+ (const FT_Byte *)newFreetype->fontData.constData(),
+ newFreetype->fontData.size(),
+ face_id.index,
+ &face)) {
return nullptr;
}
- } else if (FT_New_Face(freetypeData->library, face_id.filename, face_id.index, &face)) {
- return nullptr;
+
+ // On older Freetype versions, we create a temporary duplicate of the FT_Face to work
+ // around a bug, see further down.
+ if (goodVersion) {
+ tmpFace = face;
+ if (FT_Reference_Face(face))
+ tmpFace = nullptr;
+ } else if (!FT_HAS_MULTIPLE_MASTERS(face)
+ || FT_New_Memory_Face(freetypeData->library,
+ (const FT_Byte *)newFreetype->fontData.constData(),
+ newFreetype->fontData.size(),
+ face_id.index,
+ &tmpFace) != FT_Err_Ok) {
+ tmpFace = nullptr;
+ }
+ } else {
+ if (FT_New_Face(freetypeData->library, face_id.filename, face_id.index, &face))
+ return nullptr;
+
+ // On older Freetype versions, we create a temporary duplicate of the FT_Face to work
+ // around a bug, see further down.
+ if (goodVersion) {
+ tmpFace = face;
+ if (FT_Reference_Face(face))
+ tmpFace = nullptr;
+ } else if (!FT_HAS_MULTIPLE_MASTERS(face)
+ || FT_New_Face(freetypeData->library, face_id.filename, face_id.index, &tmpFace) != FT_Err_Ok) {
+ tmpFace = nullptr;
+ }
+ }
+
+ // Due to a bug in Freetype 2.13.2 and earlier causing just a call to FT_Get_MM_Var() on
+ // specific fonts to corrupt the FT_Face so that loading glyphs will later fail, we use a
+ // temporary FT_Face here which can be thrown away after. The bug has been fixed in
+ // Freetype 2.13.3.
+ if (tmpFace != nullptr) {
+ FT_MM_Var *var;
+ if (FT_Get_MM_Var(tmpFace, &var) == FT_Err_Ok) {
+ for (FT_UInt i = 0; i < var->num_axis; ++i) {
+ FT_Var_Axis *axis = var->axis + i;
+
+ QFontVariableAxis fontVariableAxis;
+ if (const auto tag = QFont::Tag::fromValue(axis->tag)) {
+ fontVariableAxis.setTag(*tag);
+ } else {
+ qWarning() << "QFreetypeFace::getFace: Invalid variable axis tag encountered"
+ << axis->tag;
+ }
+
+ fontVariableAxis.setMinimumValue(axis->minimum / 65536.0);
+ fontVariableAxis.setMaximumValue(axis->maximum / 65536.0);
+ fontVariableAxis.setDefaultValue(axis->def / 65536.0);
+ fontVariableAxis.setName(QString::fromUtf8(axis->name));
+
+ newFreetype->variableAxisList.append(fontVariableAxis);
+ }
+
+ if (!face_id.variableAxes.isEmpty()) {
+ QVarLengthArray<FT_Fixed, 16> coords(var->num_axis);
+ FT_Get_Var_Design_Coordinates(face, var->num_axis, coords.data());
+ for (qsizetype i = 0; i < newFreetype->variableAxisList.size(); ++i) {
+ const QFontVariableAxis &axis = newFreetype->variableAxisList.at(i);
+ if (axis.tag().isValid()) {
+ const auto it = face_id.variableAxes.constFind(axis.tag());
+ if (it != face_id.variableAxes.constEnd())
+ coords[i] = FT_Fixed(*it * 65536);
+ }
+ }
+ FT_Set_Var_Design_Coordinates(face, var->num_axis, coords.data());
+ }
+
+ FT_Done_MM_Var(qt_getFreetype(), var);
+ }
+
+ FT_Done_Face(tmpFace);
}
#if (FREETYPE_MAJOR*10000 + FREETYPE_MINOR*100 + FREETYPE_PATCH) >= 20900
@@ -328,24 +411,6 @@ QFreetypeFace *QFreetypeFace::getFace(const QFontEngine::FaceId &face_id,
FT_Set_Charmap(newFreetype->face, newFreetype->unicode_map);
- if (!face_id.variableAxes.isEmpty()) {
- FT_MM_Var *var = nullptr;
- FT_Get_MM_Var(newFreetype->face, &var);
- if (var != nullptr) {
- QVarLengthArray<FT_Fixed, 16> coords(var->num_axis);
- FT_Get_Var_Design_Coordinates(face, var->num_axis, coords.data());
- for (FT_UInt i = 0; i < var->num_axis; ++i) {
- if (const auto tag = QFont::Tag::fromValue(var->axis[i].tag)) {
- const auto it = face_id.variableAxes.constFind(*tag);
- if (it != face_id.variableAxes.constEnd())
- coords[i] = FT_Fixed(*it * 65536);
- }
- }
- FT_Set_Var_Design_Coordinates(face, var->num_axis, coords.data());
- FT_Done_MM_Var(qt_getFreetype(), var);
- }
- }
-
QT_TRY {
freetypeData->faces.insert(face_id, newFreetype.get());
} QT_CATCH(...) {
@@ -362,8 +427,8 @@ QFreetypeFace *QFreetypeFace::getFace(const QFontEngine::FaceId &face_id,
void QFreetypeFace::cleanup()
{
hbFace.reset();
- if (mm_var && face && face->glyph)
- FT_Done_MM_Var(face->glyph->library, mm_var);
+ if (mm_var)
+ FT_Done_MM_Var(qt_getFreetype(), mm_var);
mm_var = nullptr;
FT_Done_Face(face);
face = nullptr;
@@ -2312,6 +2377,11 @@ Qt::HANDLE QFontEngineFT::handle() const
return non_locked_face();
}
+QList<QFontVariableAxis> QFontEngineFT::variableAxes() const
+{
+ return freetype->variableAxes();
+}
+
QT_END_NAMESPACE
#endif // QT_NO_FREETYPE
diff --git a/src/gui/text/freetype/qfontengine_ft_p.h b/src/gui/text/freetype/qfontengine_ft_p.h
index bdd45498272..0a6aebf098f 100644
--- a/src/gui/text/freetype/qfontengine_ft_p.h
+++ b/src/gui/text/freetype/qfontengine_ft_p.h
@@ -83,6 +83,11 @@ public:
static void addGlyphToPath(FT_Face face, FT_GlyphSlot g, const QFixedPoint &point, QPainterPath *path, FT_Fixed x_scale, FT_Fixed y_scale);
static void addBitmapToPath(FT_GlyphSlot slot, const QFixedPoint &point, QPainterPath *path);
+ inline QList<QFontVariableAxis> variableAxes() const
+ {
+ return variableAxisList;
+ }
+
private:
friend class QFontEngineFT;
friend class QtFreetypeData;
@@ -94,6 +99,7 @@ private:
QByteArray fontData;
QFontEngine::Holder hbFace;
+ QList<QFontVariableAxis> variableAxisList;
};
class Q_GUI_EXPORT QFontEngineFT : public QFontEngine
@@ -214,6 +220,8 @@ private:
int glyphCount() const override;
+ QList<QFontVariableAxis> variableAxes() const override;
+
enum Scaling {
Scaled,
Unscaled
diff --git a/src/gui/text/qfont.cpp b/src/gui/text/qfont.cpp
index c8881a9bf8b..c7223566aa1 100644
--- a/src/gui/text/qfont.cpp
+++ b/src/gui/text/qfont.cpp
@@ -1,4 +1,4 @@
-// Copyright (C) 2019 The Qt Company Ltd.
+// Copyright (C) 2024 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#include "qfont.h"
@@ -3326,7 +3326,20 @@ bool QFontInfo::exactMatch() const
return d->request.exactMatch(engine->fontDef);
}
+/*!
+ \since 6.9
+
+ If the font is a variable font, this function will return the
+ list of axes the font supports.
+ See \l{QFont::}{setVariableAxis()} for more details on variable axes.
+*/
+QList<QFontVariableAxis> QFontInfo::variableAxes() const
+{
+ QFontEngine *engine = d->engineForScript(QChar::Script_Common);
+ Q_ASSERT(engine != nullptr);
+ return engine->variableAxes();
+}
// **********************************************************************
diff --git a/src/gui/text/qfontengine.cpp b/src/gui/text/qfontengine.cpp
index ee74c7f8fb8..a2ad2469688 100644
--- a/src/gui/text/qfontengine.cpp
+++ b/src/gui/text/qfontengine.cpp
@@ -1500,6 +1500,10 @@ QFixed QFontEngine::lastRightBearing(const QGlyphLayout &glyphs)
return 0;
}
+QList<QFontVariableAxis> QFontEngine::variableAxes() const
+{
+ return QList<QFontVariableAxis>{};
+}
QFontEngine::GlyphCacheEntry::GlyphCacheEntry()
{
@@ -2325,6 +2329,11 @@ QImage QFontEngineMulti::alphaRGBMapForGlyph(glyph_t glyph,
return engine(which)->alphaRGBMapForGlyph(stripped(glyph), subPixelPosition, t);
}
+QList<QFontVariableAxis> QFontEngineMulti::variableAxes() const
+{
+ return engine(0)->variableAxes();
+}
+
/*
This is used indirectly by Qt WebKit when using QTextLayout::setRawFont
diff --git a/src/gui/text/qfontengine_p.h b/src/gui/text/qfontengine_p.h
index 807f02fdc7d..ca2a9169949 100644
--- a/src/gui/text/qfontengine_p.h
+++ b/src/gui/text/qfontengine_p.h
@@ -16,6 +16,7 @@
//
#include <QtGui/private/qtguiglobal_p.h>
+#include <QtGui/qfontvariableaxis.h>
#include "QtCore/qatomic.h"
#include <QtCore/qvarlengtharray.h>
#include <QtCore/qhashfunctions.h>
@@ -229,6 +230,8 @@ public:
virtual Qt::HANDLE handle() const;
+ virtual QList<QFontVariableAxis> variableAxes() const;
+
void *harfbuzzFont() const;
void *harfbuzzFace() const;
bool supportsScript(QChar::Script script) const;
@@ -469,6 +472,8 @@ public:
virtual qreal minLeftBearing() const override;
virtual qreal minRightBearing() const override;
+ virtual QList<QFontVariableAxis> variableAxes() const override;
+
virtual bool canRender(const QChar *string, int len) const override;
inline int fallbackFamilyCount() const { return m_fallbackFamilies.size(); }
diff --git a/src/gui/text/qfontinfo.h b/src/gui/text/qfontinfo.h
index 0edee5abe52..0bc217abc87 100644
--- a/src/gui/text/qfontinfo.h
+++ b/src/gui/text/qfontinfo.h
@@ -6,6 +6,7 @@
#include <QtGui/qtguiglobal.h>
#include <QtGui/qfont.h>
+#include <QtGui/qfontvariableaxis.h>
#include <QtCore/qshareddata.h>
@@ -38,6 +39,8 @@ public:
bool fixedPitch() const;
QFont::StyleHint styleHint() const;
+ QList<QFontVariableAxis> variableAxes() const;
+
#if QT_DEPRECATED_SINCE(6, 0)
QT_DEPRECATED_VERSION_X_6_0("Use weight() instead") int legacyWeight() const;
#endif
diff --git a/src/gui/text/qfontvariableaxis.cpp b/src/gui/text/qfontvariableaxis.cpp
new file mode 100644
index 00000000000..79a05b3617e
--- /dev/null
+++ b/src/gui/text/qfontvariableaxis.cpp
@@ -0,0 +1,226 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "qfontvariableaxis.h"
+
+QT_BEGIN_NAMESPACE
+
+class QFontVariableAxisPrivate : public QSharedData
+{
+public:
+ QFont::Tag tag;
+ QString name;
+ qreal minimumValue = 0.0;
+ qreal maximumValue = 0.0;
+ qreal defaultValue = 0.0;
+};
+
+/*!
+ \class QFontVariableAxis
+ \reentrant
+ \inmodule QtGui
+ \ingroup shared
+ \since 6.9
+
+ \brief The QFontVariableAxis class represents a variable axis in a font.
+
+ Variable fonts provide a way to store multiple variations (with different weights, widths
+ or styles) in the same font file. The variations are given as floating point values for
+ a pre-defined set of parameters, called "variable axes".
+
+ Specific parameterizations (sets of values for the axes in a font) can be selected using
+ the properties in QFont, same as with traditional subfamilies that are defined as stand-alone
+ font files. But with variable fonts, arbitrary values can be provided for each axis to gain a
+ fine-grained customization of the font's appearance.
+
+ QFontVariableAxis contains information of one axis. Use \l{QFontInfo::variableAxes()}
+ to retrieve a list of the variable axes defined for a given font. Specific values can be
+ provided for an axis by using \l{QFont::setVariableAxis()} and passing in the \l{tag()}.
+
+ \note On Windows, variable axes are not supported if the optional GDI font backend is in use.
+*/
+QFontVariableAxis::QFontVariableAxis()
+ : d_ptr(new QFontVariableAxisPrivate)
+{
+}
+
+/*!
+ Destroys this QFontVariableAxis object.
+*/
+QFontVariableAxis::~QFontVariableAxis() = default;
+QT_DEFINE_QESDP_SPECIALIZATION_DTOR(QFontVariableAxisPrivate)
+
+/*!
+ Creates a QFontVariableAxis object that is a copy of the given \a axis.
+
+ \sa operator=()
+*/
+QFontVariableAxis::QFontVariableAxis(const QFontVariableAxis &axis) = default;
+
+/*!
+ Returns the tag of the axis. This is a four-character sequence which identifies the axis.
+ Certain tags have standardized meanings, such as "wght" (weight) and "wdth" (width), but any
+ sequence of four latin-1 characters is a valid tag. By convention, non-standard/custom axes
+ are denoted by tags in all uppercase.
+
+ \sa QFont::setVariableAxis(), name()
+ */
+QFont::Tag QFontVariableAxis::tag() const
+{
+ Q_D(const QFontVariableAxis);
+ return d->tag;
+}
+
+/*!
+ Sets the tag of QFontVariableAxis to \a tag.
+
+ \note Typically, there will be no need to call this function as it will not affect the font
+ itself, only this particular representation.
+
+ \sa tag()
+ */
+void QFontVariableAxis::setTag(QFont::Tag tag)
+{
+ Q_D(QFontVariableAxis);
+ if (d->tag == tag)
+ return;
+ detach();
+ d->tag = tag;
+}
+
+/*!
+ Returns the name of the axis, if provided by the font.
+
+ \sa tag()
+*/
+QString QFontVariableAxis::name() const
+{
+ Q_D(const QFontVariableAxis);
+ return d->name;
+}
+
+/*!
+ Sets the name of this QFontVariableAxis to \a name.
+
+ \note Typically, there will be no need to call this function as it will not affect the font
+ itself, only this particular representation.
+
+ \sa name()
+ */
+void QFontVariableAxis::setName(const QString &name)
+{
+ Q_D(QFontVariableAxis);
+ if (d->name == name)
+ return;
+ detach();
+ d->name = name;
+}
+
+/*!
+ Returns the minimum value of the axis. Setting the axis to a value which is lower than this
+ is not supported.
+
+ \sa maximumValue(), defaultValue()
+*/
+qreal QFontVariableAxis::minimumValue() const
+{
+ Q_D(const QFontVariableAxis);
+ return d->minimumValue;
+}
+
+/*!
+ Sets the minimum value of this QFontVariableAxis to \a minimumValue.
+
+ \note Typically, there will be no need to call this function as it will not affect the font
+ itself, only this particular representation.
+
+ \sa minimumValue()
+*/
+void QFontVariableAxis::setMinimumValue(qreal minimumValue)
+{
+ Q_D(QFontVariableAxis);
+ if (d->minimumValue == minimumValue)
+ return;
+ detach();
+ d->minimumValue = minimumValue;
+}
+
+/*!
+ Returns the maximum value of the axis. Setting the axis to a value which is higher than this
+ is not supported.
+
+ \sa minimumValue(), defaultValue()
+*/
+qreal QFontVariableAxis::maximumValue() const
+{
+ Q_D(const QFontVariableAxis);
+ return d->maximumValue;
+}
+
+/*!
+ Sets the maximum value of this QFontVariableAxis to \a maximumValue.
+
+ \note Typically, there will be no need to call this function as it will not affect the font
+ itself, only this particular representation.
+
+ \sa maximumValue()
+*/
+void QFontVariableAxis::setMaximumValue(qreal maximumValue)
+{
+ Q_D(QFontVariableAxis);
+ if (d->maximumValue == maximumValue)
+ return;
+ detach();
+ d->maximumValue = maximumValue;
+}
+
+/*!
+ Returns the default value of the axis. This is the value the axis will have if none has been
+ provided in the QFont query.
+
+ \sa minimumValue(), maximumValue()
+*/
+qreal QFontVariableAxis::defaultValue() const
+{
+ Q_D(const QFontVariableAxis);
+ return d->defaultValue;
+}
+
+/*!
+ Sets the default value of this QFontVariableAxis to \a defaultValue.
+
+ \note Typically, there will be no need to call this function as it will not affect the font
+ itself, only this particular representation.
+
+ \sa defaultValue()
+*/
+void QFontVariableAxis::setDefaultValue(qreal defaultValue)
+{
+ Q_D(QFontVariableAxis);
+ if (d->defaultValue == defaultValue)
+ return;
+ detach();
+ d->defaultValue = defaultValue;
+}
+
+/*!
+ Assigns the given \a axis to this QFontVariableAxis.
+
+ \sa QFontVariableAxis()
+*/
+QFontVariableAxis &QFontVariableAxis::operator=(const QFontVariableAxis &axis)
+{
+ QFontVariableAxis copy(axis);
+ swap(copy);
+ return *this;
+}
+
+/*!
+ \internal
+ */
+void QFontVariableAxis::detach()
+{
+ d_ptr.detach();
+}
+
+QT_END_NAMESPACE
diff --git a/src/gui/text/qfontvariableaxis.h b/src/gui/text/qfontvariableaxis.h
new file mode 100644
index 00000000000..5a2f71786bd
--- /dev/null
+++ b/src/gui/text/qfontvariableaxis.h
@@ -0,0 +1,67 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef QFONTVARIABLEAXIS_H
+#define QFONTVARIABLEAXIS_H
+
+#include <QtGui/qtguiglobal.h>
+#include <QtGui/qfont.h>
+
+#include <QtCore/qshareddata.h>
+
+QT_BEGIN_NAMESPACE
+
+class QFontVariableAxisPrivate;
+QT_DECLARE_QESDP_SPECIALIZATION_DTOR_WITH_EXPORT(QFontVariableAxisPrivate, Q_GUI_EXPORT)
+
+class Q_GUI_EXPORT QFontVariableAxis
+{
+ Q_GADGET
+ Q_DECLARE_PRIVATE(QFontVariableAxis)
+
+ Q_PROPERTY(QByteArray tag READ tagString CONSTANT)
+ Q_PROPERTY(QString name READ name CONSTANT)
+ Q_PROPERTY(qreal minimumValue READ minimumValue CONSTANT)
+ Q_PROPERTY(qreal maximumValue READ maximumValue CONSTANT)
+ Q_PROPERTY(qreal defaultValue READ defaultValue CONSTANT)
+public:
+ QFontVariableAxis();
+ QFontVariableAxis(QFontVariableAxis &&other) noexcept = default;
+ QFontVariableAxis(const QFontVariableAxis &axis);
+ ~QFontVariableAxis();
+ QT_MOVE_ASSIGNMENT_OPERATOR_IMPL_VIA_PURE_SWAP(QFontVariableAxis)
+ void swap(QFontVariableAxis &other) noexcept
+ {
+ d_ptr.swap(other.d_ptr);
+ }
+
+ QFontVariableAxis &operator=(const QFontVariableAxis &axis);
+
+ QFont::Tag tag() const;
+ void setTag(QFont::Tag tag);
+
+ QString name() const;
+ void setName(const QString &name);
+
+ qreal minimumValue() const;
+ void setMinimumValue(qreal minimumValue);
+
+ qreal maximumValue() const;
+ void setMaximumValue(qreal maximumValue);
+
+ qreal defaultValue() const;
+ void setDefaultValue(qreal defaultValue);
+
+private:
+ QByteArray tagString() const { return tag().toString(); }
+ void detach();
+
+ QExplicitlySharedDataPointer<QFontVariableAxisPrivate> d_ptr;
+};
+
+Q_DECLARE_SHARED(QFontVariableAxis)
+
+QT_END_NAMESPACE
+
+#endif // QFONTVARIABLEAXIS_H
+
diff --git a/src/gui/text/windows/qwindowsfontenginedirectwrite.cpp b/src/gui/text/windows/qwindowsfontenginedirectwrite.cpp
index 47b8a7ee3c2..65748f108e2 100644
--- a/src/gui/text/windows/qwindowsfontenginedirectwrite.cpp
+++ b/src/gui/text/windows/qwindowsfontenginedirectwrite.cpp
@@ -369,6 +369,67 @@ void QWindowsFontEngineDirectWrite::collectMetrics()
}
loadKerningPairs(emSquareSize() / QFixed::fromReal(fontDef.pixelSize));
+
+#if QT_CONFIG(directwrite3)
+ IDWriteFontFace5 *face5;
+ if (SUCCEEDED(m_directWriteFontFace->QueryInterface(__uuidof(IDWriteFontFace5),
+ reinterpret_cast<void **>(&face5)))) {
+
+ IDWriteFontResource *fontResource;
+ if (SUCCEEDED(face5->GetFontResource(&fontResource))) {
+ const UINT32 fontAxisCount = fontResource->GetFontAxisCount();
+ if (fontAxisCount > 0) {
+ QVarLengthArray<DWRITE_FONT_AXIS_VALUE, 8> axisValues(fontAxisCount);
+ HRESULT hres = fontResource->GetDefaultFontAxisValues(axisValues.data(), fontAxisCount);
+
+ QVarLengthArray<DWRITE_FONT_AXIS_RANGE, 8> axisRanges(fontAxisCount);
+ if (SUCCEEDED(hres))
+ hres = fontResource->GetFontAxisRanges(axisRanges.data(), fontAxisCount);
+
+ if (SUCCEEDED(hres)) {
+ for (UINT32 i = 0; i < fontAxisCount; ++i) {
+ const DWRITE_FONT_AXIS_VALUE &value = axisValues.at(i);
+ const DWRITE_FONT_AXIS_RANGE &range = axisRanges.at(i);
+
+ if (range.minValue < range.maxValue) {
+ QFontVariableAxis axis;
+ if (auto maybeTag = QFont::Tag::fromValue(qToBigEndian<UINT32>(value.axisTag))) {
+ axis.setTag(*maybeTag);
+ } else {
+ qWarning() << "QWindowsFontEngineDirectWrite::collectMetrics: Invalid tag" << value.axisTag;
+ }
+
+ axis.setDefaultValue(value.value);
+ axis.setMaximumValue(range.maxValue);
+ axis.setMinimumValue(range.minValue);
+
+ IDWriteLocalizedStrings *names;
+ if (SUCCEEDED(fontResource->GetAxisNames(i, &names))) {
+ wchar_t defaultLocale[LOCALE_NAME_MAX_LENGTH];
+ bool hasDefaultLocale = GetUserDefaultLocaleName(defaultLocale, LOCALE_NAME_MAX_LENGTH) != 0;
+
+ QString name = hasDefaultLocale
+ ? QWindowsDirectWriteFontDatabase::localeString(names, defaultLocale)
+ : QString();
+ if (name.isEmpty()) {
+ wchar_t englishLocale[] = L"en-us";
+ name = QWindowsDirectWriteFontDatabase::localeString(names, englishLocale);
+ }
+
+ axis.setName(name);
+ names->Release();
+ }
+
+ m_variableAxes.append(axis);
+ }
+ }
+ }
+ }
+ fontResource->Release();
+ }
+ face5->Release();
+ }
+#endif
}
QFixed QWindowsFontEngineDirectWrite::underlinePosition() const
@@ -1177,4 +1238,9 @@ QImage QWindowsFontEngineDirectWrite::bitmapForGlyph(glyph_t glyph,
return imageForGlyph(glyph, subPixelPosition, glyphMargin(QFontEngine::Format_ARGB), t, color);
}
+QList<QFontVariableAxis> QWindowsFontEngineDirectWrite::variableAxes() const
+{
+ return m_variableAxes;
+}
+
QT_END_NAMESPACE
diff --git a/src/gui/text/windows/qwindowsfontenginedirectwrite_p.h b/src/gui/text/windows/qwindowsfontenginedirectwrite_p.h
index d7c9a792677..5c98773dad3 100644
--- a/src/gui/text/windows/qwindowsfontenginedirectwrite_p.h
+++ b/src/gui/text/windows/qwindowsfontenginedirectwrite_p.h
@@ -102,6 +102,8 @@ public:
Properties properties() const override;
void getUnscaledGlyph(glyph_t glyph, QPainterPath *path, glyph_metrics_t *metrics) override;
+ QList<QFontVariableAxis> variableAxes() const override;
+
private:
QImage imageForGlyph(glyph_t t,
const QFixedPoint &subPixelPosition,
@@ -133,6 +135,7 @@ private:
QFixed m_maxAdvanceWidth;
FaceId m_faceId;
QString m_uniqueFamilyName;
+ QList<QFontVariableAxis> m_variableAxes;
};
QT_END_NAMESPACE
diff --git a/tests/auto/gui/text/qfont/CMakeLists.txt b/tests/auto/gui/text/qfont/CMakeLists.txt
index 88ae9959e48..296303b9fce 100644
--- a/tests/auto/gui/text/qfont/CMakeLists.txt
+++ b/tests/auto/gui/text/qfont/CMakeLists.txt
@@ -12,9 +12,14 @@ if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT)
endif()
# Resources:
+set_source_files_properties("../../../shared/resources/testfont_variable.ttf"
+ PROPERTIES QT_RESOURCE_ALIAS "testfont_variable.ttf"
+)
+
set(testfont_resource_files
"datastream.515"
"weirdfont.otf"
+ "../../../shared/resources/testfont_variable.ttf"
)
qt_internal_add_test(tst_qfont
diff --git a/tests/auto/gui/text/qfont/tst_qfont.cpp b/tests/auto/gui/text/qfont/tst_qfont.cpp
index 5426d7b1174..95de6567211 100644
--- a/tests/auto/gui/text/qfont/tst_qfont.cpp
+++ b/tests/auto/gui/text/qfont/tst_qfont.cpp
@@ -22,6 +22,9 @@
#include <qlist.h>
#include <QtTest/private/qemulationdetector_p.h>
#include <private/qcomparisontesthelper_p.h>
+#include <qpa/qplatformfontdatabase.h>
+#include <qpa/qplatformintegration.h>
+#include <QtGui/private/qguiapplication_p.h>
using namespace Qt::StringLiterals;
@@ -30,6 +33,7 @@ class tst_QFont : public QObject
Q_OBJECT
private slots:
+ void initTestCase();
void getSetCheck();
void exactMatch();
void compare();
@@ -62,8 +66,18 @@ private slots:
void featureAccessors();
void tagCompares_data();
void tagCompares();
+
+ void variableAxes();
+
+private:
+ QString m_testFontVariable;
};
+void tst_QFont::initTestCase()
+{
+ m_testFontVariable = QFINDTESTDATA("testfont_variable.ttf");
+}
+
// Testing get/set functions
void tst_QFont::getSetCheck()
{
@@ -927,5 +941,34 @@ void tst_QFont::tagCompares()
QCOMPARE(compareThreeWay(lhs, rhs), expectedOrder);
}
+void tst_QFont::variableAxes()
+{
+ {
+ QPlatformFontDatabase *pfdb = QGuiApplicationPrivate::platformIntegration()->fontDatabase();
+ if (!pfdb->supportsVariableApplicationFonts())
+ QSKIP("Variable application fonts not supported on this platform");
+ }
+
+ int id = QFontDatabase::addApplicationFont(m_testFontVariable);
+ if (id == -1)
+ QSKIP("Application fonts are not supported on this system");
+ auto cleanup = qScopeGuard([&id] {
+ QFontDatabase::removeApplicationFont(id);
+ });
+
+ QString family = QFontDatabase::applicationFontFamilies(id).first();
+ QFontInfo fontInfo(QFont(family, 12));
+
+ QList<QFontVariableAxis> variableAxes = fontInfo.variableAxes();
+ QCOMPARE(variableAxes.size(), 1);
+
+ const QFontVariableAxis &variableAxis = variableAxes.first();
+ QCOMPARE(variableAxis.name(), QStringLiteral("Weight"));
+ QCOMPARE(variableAxis.tag(), QFont::Tag::fromString("wght"));
+ QCOMPARE(variableAxis.defaultValue(), 400.0);
+ QCOMPARE(variableAxis.minimumValue(), 400.0);
+ QCOMPARE(variableAxis.maximumValue(), 900.0);
+}
+
QTEST_MAIN(tst_QFont)
#include "tst_qfont.moc"