From fb89d498a967a53b4347f6db5f9a5426d60d2a01 Mon Sep 17 00:00:00 2001 From: Eskil Abrahamsen Blomfeldt Date: Mon, 2 Jun 2025 12:38:55 +0200 Subject: [PATCH] Add support for font features and variable axes to QTextCharFormat These can be set on the font directly, but had not been added to QTextCharFormat, so there would be no way to override them by formatting in a rich text document. Fixes: QTBUG-134060 Change-Id: I4494e24cb9b99d84fb376ba895e2461fc3cd054b Reviewed-by: Eirik Aavitsland --- src/corelib/serialization/qdatastream.h | 2 +- src/gui/text/qtextformat.cpp | 112 ++++++++++++- src/gui/text/qtextformat.h | 10 +- .../qdatastream/tst_qdatastream.cpp | 1 + .../gui/text/qtextformat/tst_qtextformat.cpp | 152 ++++++++++++++++++ 5 files changed, 267 insertions(+), 10 deletions(-) diff --git a/src/corelib/serialization/qdatastream.h b/src/corelib/serialization/qdatastream.h index ea8ab1f16f0..aed3888b5c6 100644 --- a/src/corelib/serialization/qdatastream.h +++ b/src/corelib/serialization/qdatastream.h @@ -91,7 +91,7 @@ public: Qt_6_8 = Qt_6_7, Qt_6_9 = Qt_6_7, Qt_6_10 = 23, - Qt_6_11 = Qt_6_10, + Qt_6_11 = 24, Qt_DefaultCompiledVersion = Qt_6_11 #if QT_VERSION >= QT_VERSION_CHECK(6, 12, 0) #error Add the datastream version for this Qt version and update Qt_DefaultCompiledVersion diff --git a/src/gui/text/qtextformat.cpp b/src/gui/text/qtextformat.cpp index 73b0e769169..06ae00123f0 100644 --- a/src/gui/text/qtextformat.cpp +++ b/src/gui/text/qtextformat.cpp @@ -388,6 +388,20 @@ void QTextFormatPrivate::recalcFont() const case QTextFormat::FontKerning: f.setKerning(props.at(i).value.toBool()); break; + case QTextFormat::FontFeatures: + { + const auto fontFeatures = props.at(i).value.value>(); + for (auto it = fontFeatures.constBegin(); it != fontFeatures.constEnd(); ++it) + f.setFeature(it.key(), it.value()); + break; + } + case QTextFormat::FontVariableAxes: + { + const auto fontVariableAxes = props.at(i).value.value>(); + for (auto it = fontVariableAxes.constBegin(); it != fontVariableAxes.constEnd(); ++it) + f.setVariableAxis(it.key(), it.value()); + break; + } default: break; } @@ -404,6 +418,16 @@ void QTextFormatPrivate::recalcFont() const Q_GUI_EXPORT QDataStream &operator<<(QDataStream &stream, const QTextFormat &fmt) { QMap properties = fmt.properties(); + if (stream.version() < QDataStream::Qt_6_11) { + auto it = properties.constFind(QTextFormat::FontFeatures); + if (it != properties.cend()) + properties.erase(it); + + it = properties.constFind(QTextFormat::FontVariableAxes); + if (it != properties.cend()) + properties.erase(it); + } + if (stream.version() < QDataStream::Qt_6_0) { auto it = properties.constFind(QTextFormat::FontLetterSpacingType); if (it != properties.cend()) { @@ -447,14 +471,17 @@ Q_GUI_EXPORT QDataStream &operator>>(QDataStream &stream, QTextFormat &fmt) for (QMap::ConstIterator it = properties.constBegin(); it != properties.constEnd(); ++it) { qint32 key = it.key(); - if (key == QTextFormat::OldFontLetterSpacingType) - key = QTextFormat::FontLetterSpacingType; - else if (key == QTextFormat::OldFontStretch) - key = QTextFormat::FontStretch; - else if (key == QTextFormat::OldTextUnderlineColor) - key = QTextFormat::TextUnderlineColor; - else if (key == QTextFormat::OldFontFamily) - key = QTextFormat::FontFamilies; + + if (stream.version() < QDataStream::Qt_6_0) { + if (key == QTextFormat::OldFontLetterSpacingType) + key = QTextFormat::FontLetterSpacingType; + else if (key == QTextFormat::OldFontStretch) + key = QTextFormat::FontStretch; + else if (key == QTextFormat::OldTextUnderlineColor) + key = QTextFormat::TextUnderlineColor; + else if (key == QTextFormat::OldFontFamily) + key = QTextFormat::FontFamilies; + } fmt.d->insertProperty(key, it.value()); } @@ -653,6 +680,10 @@ Q_GUI_EXPORT QDataStream &operator>>(QDataStream &stream, QTextTableCellFormat & \value FontKerning Specifies whether the font has kerning turned on. \value FontHintingPreference Controls the use of hinting according to values of the QFont::HintingPreference enum. + \value FontFeatures [since 6.11] Assigns integer numbers to typographical features. See + \l{QFont::setFeature()} for additional information. + \value FontVariableAxes [since 6.11] Assigns floating point numbers to variable axes in variable + fonts. See \l{QFont::setVariableAxis()} for additional information. \omitvalue FirstFontProperty \omitvalue LastFontProperty @@ -1809,6 +1840,55 @@ void QTextCharFormat::setUnderlineStyle(UnderlineStyle style) \sa font(), QFont::hintingPreference() */ +/*! + \since 6.11 + + Sets the typographical features of the text format's font to be \a fontFeatures. + + \sa QFont::setFeature() +*/ +void QTextCharFormat::setFontFeatures(const QHash &fontFeatures) +{ + setProperty(FontFeatures, QVariant::fromValue(fontFeatures)); +} + +/*! + \since 6.11 + + Gets the typographical features of the text format's font. + + \sa setFontFeatures() +*/ +QHash QTextCharFormat::fontFeatures() const +{ + return property(FontFeatures).value>(); +} + +/*! + \since 6.11 + + Sets the variable axes of the text format's font to be \a fontVariableAxes. + + \sa QFont::setVariableAxis() +*/ +void QTextCharFormat::setFontVariableAxes(const QHash &fontVariableAxes) +{ + setProperty(FontVariableAxes, QVariant::fromValue(fontVariableAxes)); +} + +/*! + \since 6.11 + + Gets the variable axes of the text format's font. + + \sa setFontVariableAxes() +*/ +QHash QTextCharFormat::fontVariableAxes() const +{ + return property(FontVariableAxes).value>(); +} + + /*! \fn QPen QTextCharFormat::textOutline() const @@ -2145,6 +2225,22 @@ void QTextCharFormat::setFont(const QFont &font, FontPropertiesInheritanceBehavi setFontHintingPreference(font.hintingPreference()); if (mask & QFont::KerningResolved) setFontKerning(font.kerning()); + if (mask & QFont::FeaturesResolved) { + const auto tags = font.featureTags(); + + QHash fontFeatures; + for (QFont::Tag tag : tags) + fontFeatures.insert(tag, font.featureValue(tag)); + setFontFeatures(fontFeatures); + } + if (mask & QFont::VariableAxesResolved) { + const auto tags = font.variableAxisTags(); + + QHash fontVariableAxes; + for (QFont::Tag tag : tags) + fontVariableAxes.insert(tag, font.variableAxisValue(tag)); + setFontVariableAxes(fontVariableAxes); + } } /*! diff --git a/src/gui/text/qtextformat.h b/src/gui/text/qtextformat.h index 2fa86ed0d1b..d521968ad6c 100644 --- a/src/gui/text/qtextformat.h +++ b/src/gui/text/qtextformat.h @@ -14,6 +14,7 @@ #include #include #include +#include QT_BEGIN_NAMESPACE @@ -159,7 +160,9 @@ public: FontStrikeOut = 0x2007, FontFixedPitch = 0x2008, FontPixelSize = 0x2009, - LastFontProperty = FontPixelSize, + FontFeatures = 0x2010, // Note: Same as OldTextUnderlineColor + FontVariableAxes = 0x2011, + LastFontProperty = FontVariableAxes, TextUnderlineColor = 0x2020, TextVerticalAlignment = 0x2021, @@ -518,6 +521,11 @@ public: return static_cast(intProperty(FontHintingPreference)); } + void setFontFeatures(const QHash &fontFeatures); + QHash fontFeatures() const; + void setFontVariableAxes(const QHash &fontVariableAxes); + QHash fontVariableAxes() const; + inline void setFontKerning(bool enable) { setProperty(FontKerning, enable); } inline bool fontKerning() const diff --git a/tests/auto/corelib/serialization/qdatastream/tst_qdatastream.cpp b/tests/auto/corelib/serialization/qdatastream/tst_qdatastream.cpp index 37cc4252247..1abd2939c43 100644 --- a/tests/auto/corelib/serialization/qdatastream/tst_qdatastream.cpp +++ b/tests/auto/corelib/serialization/qdatastream/tst_qdatastream.cpp @@ -301,6 +301,7 @@ static constexpr int NColorRoles[] = { QPalette::Accent + 1, // Qt_6_6 QPalette::Accent + 1, // Qt_6_7 QPalette::Accent + 1, // Qt_6_10 + QPalette::Accent + 1, // Qt_6_11 }; // +1, because we start from "No Version" diff --git a/tests/auto/gui/text/qtextformat/tst_qtextformat.cpp b/tests/auto/gui/text/qtextformat/tst_qtextformat.cpp index 6c6145561d8..e71ea6a732c 100644 --- a/tests/auto/gui/text/qtextformat/tst_qtextformat.cpp +++ b/tests/auto/gui/text/qtextformat/tst_qtextformat.cpp @@ -45,6 +45,8 @@ private slots: void setFont_collection_data(); void setFont_collection(); void clearCollection(); + void setFontFeatures(); + void setFontVariableAxes(); #ifndef QT_NO_DATASTREAM void dataStreamCompatibility(); @@ -667,6 +669,89 @@ void tst_QTextFormat::clearCollection() QCOMPARE(collection.defaultFont(), f); // kept, QTextDocument::clear or setPlainText should not reset the font set by setDefaultFont } +void tst_QTextFormat::setFontFeatures() +{ + { + QFont font; + font.setFeature("abcd", 1234); + font.setFeature("efgh", 5678); + + QTextCharFormat format; + format.setFont(font); + + QFont resolvedFont = format.font(); + QCOMPARE(resolvedFont.featureTags().size(), 2); + QCOMPARE(resolvedFont.featureValue("abcd"), 1234); + QCOMPARE(resolvedFont.featureValue("efgh"), 5678); + + QHash features = format.fontFeatures(); + QCOMPARE(features.size(), 2); + QCOMPARE(features.value("abcd"), 1234); + QCOMPARE(features.value("efgh"), 5678); + } + + { + QTextCharFormat format; + + QHash features; + features.insert("abcd", 4321); + features.insert("efgh", 8765); + format.setFontFeatures(features); + + QFont resolvedFont = format.font(); + QCOMPARE(resolvedFont.featureTags().size(), 2); + QCOMPARE(resolvedFont.featureValue("abcd"), 4321); + QCOMPARE(resolvedFont.featureValue("efgh"), 8765); + + features = format.fontFeatures(); + QCOMPARE(features.size(), 2); + QCOMPARE(features.value("abcd"), 4321); + QCOMPARE(features.value("efgh"), 8765); + } +} + +void tst_QTextFormat::setFontVariableAxes() +{ + { + QFont font; + font.setVariableAxis("abcd", 12.25); + font.setVariableAxis("efgh", 13.25); + + QTextCharFormat format; + format.setFont(font); + + QFont resolvedFont = format.font(); + QCOMPARE(resolvedFont.variableAxisTags().size(), 2); + QCOMPARE(resolvedFont.variableAxisValue("abcd"), 12.25); + QCOMPARE(resolvedFont.variableAxisValue("efgh"), 13.25); + + QHash axes = format.fontVariableAxes(); + QCOMPARE(axes.size(), 2); + QCOMPARE(axes.value("abcd"), 12.25); + QCOMPARE(axes.value("efgh"), 13.25); + } + + { + QTextCharFormat format; + + QHash axes; + axes.insert("abcd", 12.25); + axes.insert("efgh", 13.25); + format.setFontVariableAxes(axes); + + QFont resolvedFont = format.font(); + QCOMPARE(resolvedFont.variableAxisTags().size(), 2); + QCOMPARE(resolvedFont.variableAxisValue("abcd"), 12.25); + QCOMPARE(resolvedFont.variableAxisValue("efgh"), 13.25); + + axes = format.fontVariableAxes(); + QCOMPARE(axes.size(), 2); + QCOMPARE(axes.value("abcd"), 12.25); + QCOMPARE(axes.value("efgh"), 13.25); + } + +} + #ifndef QT_NO_DATASTREAM void tst_QTextFormat::dataStreamCompatibility() { @@ -795,6 +880,73 @@ void tst_QTextFormat::dataStreamCompatibility() } } + // Don't mix up FontFeatures and OldTextUnderlineColor + memory.clear(); + { + { + QBuffer buffer(&memory); + buffer.open(QIODevice::WriteOnly); + + QFont font; + font.setFeature("abcd", 1234); + + QTextCharFormat format; + format.setFont(font); + + QDataStream stream(&buffer); + + stream << format; + } + + { + QBuffer buffer(&memory); + buffer.open(QIODevice::ReadOnly); + + QDataStream stream(&buffer); + + QTextFormat other; + stream >> other; + + QMap properties = other.properties(); + QVERIFY(properties.contains(QTextFormat::FontFeatures)); + + auto features = other.property(QTextFormat::FontFeatures).value>(); + QCOMPARE(features.value("abcd"), 1234); + } + } + + memory.clear(); + { + { + QBuffer buffer(&memory); + buffer.open(QIODevice::WriteOnly); + + QFont font; + font.setFeature("abcd", 1234); + + QTextCharFormat format; + format.setFont(font); + + QDataStream stream(&buffer); + stream.setVersion(QDataStream::Qt_5_15); + + stream << format; + } + + { + QBuffer buffer(&memory); + buffer.open(QIODevice::ReadOnly); + + QDataStream stream(&buffer); + stream.setVersion(QDataStream::Qt_5_15); + + QTextFormat other; + stream >> other; + QMap properties = other.properties(); + QVERIFY(!properties.contains(QTextFormat::FontFeatures)); + QVERIFY(!properties.contains(QTextFormat::OldTextUnderlineColor)); + } + } } #endif // QT_NO_DATASTREAM