diff options
author | Eskil Abrahamsen Blomfeldt <[email protected]> | 2024-07-23 13:25:35 +0200 |
---|---|---|
committer | Eskil Abrahamsen Blomfeldt <[email protected]> | 2024-08-28 01:49:12 +0200 |
commit | cc128d802c6d9c87a1e00a8a88d5e6590a7195f4 (patch) | |
tree | 070a766303bff0f805e61be5845987d6b4eb132c | |
parent | bfe8ac4ebff7a1e8114068f2f46e73c588735690 (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.txt | 1 | ||||
-rw-r--r-- | src/gui/text/coretext/qfontengine_coretext.mm | 49 | ||||
-rw-r--r-- | src/gui/text/coretext/qfontengine_coretext_p.h | 3 | ||||
-rw-r--r-- | src/gui/text/freetype/qfontengine_ft.cpp | 116 | ||||
-rw-r--r-- | src/gui/text/freetype/qfontengine_ft_p.h | 8 | ||||
-rw-r--r-- | src/gui/text/qfont.cpp | 15 | ||||
-rw-r--r-- | src/gui/text/qfontengine.cpp | 9 | ||||
-rw-r--r-- | src/gui/text/qfontengine_p.h | 5 | ||||
-rw-r--r-- | src/gui/text/qfontinfo.h | 3 | ||||
-rw-r--r-- | src/gui/text/qfontvariableaxis.cpp | 226 | ||||
-rw-r--r-- | src/gui/text/qfontvariableaxis.h | 67 | ||||
-rw-r--r-- | src/gui/text/windows/qwindowsfontenginedirectwrite.cpp | 66 | ||||
-rw-r--r-- | src/gui/text/windows/qwindowsfontenginedirectwrite_p.h | 3 | ||||
-rw-r--r-- | tests/auto/gui/text/qfont/CMakeLists.txt | 5 | ||||
-rw-r--r-- | tests/auto/gui/text/qfont/tst_qfont.cpp | 43 |
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" |