From 97f73e957756753b09a778daf2ee8f0ddb97f746 Mon Sep 17 00:00:00 2001 From: Andy Shaw Date: Sat, 15 Sep 2018 00:12:42 +0200 Subject: Handle fonts that have commas/quotes in the family name Since the comma character was originally used as a separator, we need to extend QFont to have setFamilies() so that we can avoid joining the family strings together. This enables us to see the family name as a single string and for multiple family names, we have families(). Subsequently, this has added functions to QTextCharFormat to account for multiple font families too. So it is now possible to set a single one directly with setFontFamily() and multiple ones with setFontFamilies(). This also bumps up the datastream version to 19 as QFont now streams the families list as well. [ChangeLog][QtGui][QFont] Add setFamilies()/families() to aid using of font families with commas and quotes in their name. [ChangeLog][Important Behavior Changes] QDataStream version bumped up to 19 to account for changes in the serialization of QFont. Fixes: QTBUG-46322 Change-Id: Iee9f715e47544a7a705c7f36401aba216a7d42b0 Reviewed-by: Lars Knoll Reviewed-by: Allan Sandfeld Jensen --- src/gui/text/qcssparser.cpp | 11 +++++-- src/gui/text/qfont.cpp | 67 +++++++++++++++++++++++++++++++++++++++- src/gui/text/qfont.h | 6 +++- src/gui/text/qfont_p.h | 4 +++ src/gui/text/qfontdatabase.cpp | 54 ++++++++++++++++---------------- src/gui/text/qfontengine.cpp | 2 +- src/gui/text/qtextformat.cpp | 23 ++++++++++++++ src/gui/text/qtextformat.h | 6 ++++ src/gui/text/qtexthtmlparser.cpp | 3 +- 9 files changed, 143 insertions(+), 33 deletions(-) (limited to 'src/gui') diff --git a/src/gui/text/qcssparser.cpp b/src/gui/text/qcssparser.cpp index 8e317d5b97..91fa40eddf 100644 --- a/src/gui/text/qcssparser.cpp +++ b/src/gui/text/qcssparser.cpp @@ -1200,11 +1200,13 @@ static bool setFontWeightFromValue(const QCss::Value &value, QFont *font) static bool setFontFamilyFromValues(const QVector &values, QFont *font, int start = 0) { QString family; + QStringList families; bool shouldAddSpace = false; for (int i = start; i < values.count(); ++i) { const QCss::Value &v = values.at(i); if (v.type == Value::TermOperatorComma) { - family += QLatin1Char(','); + families << family; + family.clear(); shouldAddSpace = false; continue; } @@ -1216,9 +1218,12 @@ static bool setFontFamilyFromValues(const QVector &values, QFont *f family += str; shouldAddSpace = true; } - if (family.isEmpty()) + if (!family.isEmpty()) + families << family; + if (families.isEmpty()) return false; - font->setFamily(family); + font->setFamily(families.at(0)); + font->setFamilies(families); return true; } diff --git a/src/gui/text/qfont.cpp b/src/gui/text/qfont.cpp index a23ef95fde..258a9ba675 100644 --- a/src/gui/text/qfont.cpp +++ b/src/gui/text/qfont.cpp @@ -116,7 +116,17 @@ bool QFontDef::exactMatch(const QFontDef &other) const if (stretch != 0 && other.stretch != 0 && stretch != other.stretch) return false; + if (families.size() != other.families.size()) + return false; + QString this_family, this_foundry, other_family, other_foundry; + for (int i = 0; i < families.size(); ++i) { + QFontDatabase::parseFontName(families.at(i), this_foundry, this_family); + QFontDatabase::parseFontName(other.families.at(i), other_foundry, other_family); + if (this_family != other_family || this_foundry != other_foundry) + return false; + } + QFontDatabase::parseFontName(family, this_foundry, this_family); QFontDatabase::parseFontName(other.family, other_foundry, other_family); @@ -261,6 +271,9 @@ void QFontPrivate::resolve(uint mask, const QFontPrivate *other) if (! (mask & QFont::FamilyResolved)) request.family = other->request.family; + if (!(mask & QFont::FamiliesResolved)) + request.families = other->request.families; + if (! (mask & QFont::StyleNameResolved)) request.styleName = other->request.styleName; @@ -424,7 +437,8 @@ QFontEngineData::~QFontEngineData() \target fontmatching The font matching algorithm works as follows: \list 1 - \li If the specified font family exists and can be used to represent + \li The specified font families (set by setFamilies()) are searched for. + \li If not found, then if set the specified font family exists and can be used to represent the writing system in use, it will be selected. \li If not, a replacement font that supports the writing system is selected. The font matching algorithm will try to find the @@ -506,6 +520,7 @@ QFontEngineData::~QFontEngineData() individually and then considered resolved. \value FamilyResolved + \value FamiliesResolved \value SizeResolved \value StyleHintResolved \value StyleStrategyResolved @@ -1691,6 +1706,7 @@ bool QFont::operator<(const QFont &f) const if (r1.stretch != r2.stretch) return r1.stretch < r2.stretch; if (r1.styleHint != r2.styleHint) return r1.styleHint < r2.styleHint; if (r1.styleStrategy != r2.styleStrategy) return r1.styleStrategy < r2.styleStrategy; + if (r1.families != r2.families) return r1.families < r2.families; if (r1.family != r2.family) return r1.family < r2.family; if (f.d->capital != d->capital) return f.d->capital < d->capital; @@ -2192,6 +2208,48 @@ QString QFont::lastResortFont() const } #endif +/*! + \since 5.13 + + Returns the requested font family names, i.e. the names set in the last + setFamilies() call or via the constructor. Otherwise it returns an + empty list. + + \sa setFamily(), setFamilies(), family(), substitutes(), substitute() +*/ + +QStringList QFont::families() const +{ + return d->request.families; +} + +/*! + \since 5.13 + + Sets the list of family names for the font. The names are case + insensitive and may include a foundry name. The first family in + \a families will be set as the main family for the font. + + Each family name entry in \a families may optionally also include a + foundry name, e.g. "Helvetica [Cronyx]". If the family is + available from more than one foundry and the foundry isn't + specified, an arbitrary foundry is chosen. If the family isn't + available a family will be set using the \l{QFont}{font matching} + algorithm. + + \sa family(), families(), setFamily(), setStyleHint(), QFontInfo +*/ + +void QFont::setFamilies(const QStringList &families) +{ + if ((resolve_mask & QFont::FamiliesResolved) && d->request.families == families) + return; + detach(); + d->request.families = families; + resolve_mask |= QFont::FamiliesResolved; +} + + /***************************************************************************** QFont stream functions *****************************************************************************/ @@ -2256,6 +2314,8 @@ QDataStream &operator<<(QDataStream &s, const QFont &font) s << (quint8)font.d->request.hintingPreference; if (s.version() >= QDataStream::Qt_5_6) s << (quint8)font.d->capital; + if (s.version() >= QDataStream::Qt_5_13) + s << font.d->request.families; return s; } @@ -2351,6 +2411,11 @@ QDataStream &operator>>(QDataStream &s, QFont &font) s >> value; font.d->capital = QFont::Capitalization(value); } + if (s.version() >= QDataStream::Qt_5_13) { + QStringList value; + s >> value; + font.d->request.families = value; + } return s; } diff --git a/src/gui/text/qfont.h b/src/gui/text/qfont.h index a94586166e..1fe450e002 100644 --- a/src/gui/text/qfont.h +++ b/src/gui/text/qfont.h @@ -164,7 +164,8 @@ public: WordSpacingResolved = 0x4000, HintingPreferenceResolved = 0x8000, StyleNameResolved = 0x10000, - AllPropertiesResolved = 0x1ffff + FamiliesResolved = 0x20000, + AllPropertiesResolved = 0x3ffff }; QFont(); @@ -179,6 +180,9 @@ public: QString family() const; void setFamily(const QString &); + QStringList families() const; + void setFamilies(const QStringList &); + QString styleName() const; void setStyleName(const QString &); diff --git a/src/gui/text/qfont_p.h b/src/gui/text/qfont_p.h index 350ba7ce3c..09c18de5a6 100644 --- a/src/gui/text/qfont_p.h +++ b/src/gui/text/qfont_p.h @@ -78,6 +78,7 @@ struct QFontDef } QString family; + QStringList families; QString styleName; QStringList fallBackFamilies; @@ -109,6 +110,7 @@ struct QFontDef && styleStrategy == other.styleStrategy && ignorePitch == other.ignorePitch && fixedPitch == other.fixedPitch && family == other.family + && families == other.families && styleName == other.styleName && hintingPreference == other.hintingPreference ; @@ -122,6 +124,7 @@ struct QFontDef if (styleHint != other.styleHint) return styleHint < other.styleHint; if (styleStrategy != other.styleStrategy) return styleStrategy < other.styleStrategy; if (family != other.family) return family < other.family; + if (families != other.families) return families < other.families; if (styleName != other.styleName) return styleName < other.styleName; if (hintingPreference != other.hintingPreference) return hintingPreference < other.hintingPreference; @@ -144,6 +147,7 @@ inline uint qHash(const QFontDef &fd, uint seed = 0) Q_DECL_NOTHROW ^ qHash(fd.ignorePitch) ^ qHash(fd.fixedPitch) ^ qHash(fd.family, seed) + ^ qHash(fd.families, seed) ^ qHash(fd.styleName) ^ qHash(fd.hintingPreference) ; diff --git a/src/gui/text/qfontdatabase.cpp b/src/gui/text/qfontdatabase.cpp index 196eebb353..42e7871214 100644 --- a/src/gui/text/qfontdatabase.cpp +++ b/src/gui/text/qfontdatabase.cpp @@ -696,20 +696,20 @@ static QStringList familyList(const QFontDef &req) { // list of families to try QStringList family_list; - if (req.family.isEmpty()) - return family_list; - const auto list = req.family.splitRef(QLatin1Char(',')); - const int numFamilies = list.size(); - family_list.reserve(numFamilies); - for (int i = 0; i < numFamilies; ++i) { - QStringRef str = list.at(i).trimmed(); - if ((str.startsWith(QLatin1Char('"')) && str.endsWith(QLatin1Char('"'))) - || (str.startsWith(QLatin1Char('\'')) && str.endsWith(QLatin1Char('\'')))) - str = str.mid(1, str.length() - 2); - family_list << str.toString(); + family_list << req.families; + if (!req.family.isEmpty()) { + const auto list = req.family.splitRef(QLatin1Char(',')); + const int numFamilies = list.size(); + family_list.reserve(numFamilies); + for (int i = 0; i < numFamilies; ++i) { + QStringRef str = list.at(i).trimmed(); + if ((str.startsWith(QLatin1Char('"')) && str.endsWith(QLatin1Char('"'))) + || (str.startsWith(QLatin1Char('\'')) && str.endsWith(QLatin1Char('\'')))) + str = str.mid(1, str.length() - 2); + family_list << str.toString(); + } } - // append the substitute list for each family in family_list for (int i = 0, size = family_list.size(); i < size; ++i) family_list += QFont::substitutes(family_list.at(i)); @@ -2688,9 +2688,8 @@ QFontEngine *QFontDatabase::findFont(const QFontDef &request, int script) } QString family_name, foundry_name; - - parseFontName(request.family, foundry_name, family_name); - + const QString requestFamily = request.families.size() > 0 ? request.families.at(0) : request.family; + parseFontName(requestFamily, foundry_name, family_name); QtFontDesc desc; QList blackListed; int index = match(multi ? QChar::Script_Common : script, request, family_name, foundry_name, &desc, blackListed); @@ -2703,8 +2702,8 @@ QFontEngine *QFontDatabase::findFont(const QFontDef &request, int script) // Don't pass empty family names to the platform font database, since it will then invoke its own matching // and we will be out of sync with the matched font. - if (fontDef.family.isEmpty()) - fontDef.family = desc.family->name; + if (fontDef.families.isEmpty() && fontDef.family.isEmpty()) + fontDef.families = QStringList(desc.family->name); engine = loadEngine(script, fontDef, desc.family, desc.foundry, desc.style, desc.size); @@ -2717,13 +2716,13 @@ QFontEngine *QFontDatabase::findFont(const QFontDef &request, int script) } if (!engine) { - if (!request.family.isEmpty()) { + if (!requestFamily.isEmpty()) { QFont::StyleHint styleHint = QFont::StyleHint(request.styleHint); if (styleHint == QFont::AnyStyle && request.fixedPitch) styleHint = QFont::TypeWriter; QStringList fallbacks = request.fallBackFamilies - + fallbacksForFamily(request.family, + + fallbacksForFamily(requestFamily, QFont::Style(request.style), styleHint, QChar::Script(script)); @@ -2741,7 +2740,7 @@ QFontEngine *QFontDatabase::findFont(const QFontDef &request, int script) index = match(multi ? QChar::Script_Common : script, def, def.family, QLatin1String(""), &desc, blackListed); if (index >= 0) { QFontDef loadDef = def; - if (loadDef.family.isEmpty()) + if (loadDef.families.isEmpty() && loadDef.family.isEmpty()) loadDef.family = desc.family->name; engine = loadEngine(script, loadDef, desc.family, desc.foundry, desc.style, desc.size); if (engine) @@ -2782,7 +2781,10 @@ void QFontDatabase::load(const QFontPrivate *d, int script) // look for the requested font in the engine data cache // note: fallBackFamilies are not respected in the EngineData cache key; // join them with the primary selection family to avoid cache misses - req.family = fallBackFamilies.join(QLatin1Char(',')); + if (!d->request.family.isEmpty()) + req.family = fallBackFamilies.join(QLatin1Char(',')); + if (!d->request.families.isEmpty()) + req.families = fallBackFamilies; d->engineData = fontCache->findEngineData(req); if (!d->engineData) { @@ -2803,14 +2805,14 @@ void QFontDatabase::load(const QFontPrivate *d, int script) req.fallBackFamilies = fallBackFamilies; if (!req.fallBackFamilies.isEmpty()) - req.family = req.fallBackFamilies.takeFirst(); + req.families = QStringList(req.fallBackFamilies.takeFirst()); // list of families to try QStringList family_list; - if (!req.family.isEmpty()) { + if (!req.families.isEmpty()) { // Add primary selection - family_list << req.family; + family_list << req.families.at(0); // add the default family QString defaultFamily = QGuiApplication::font().family(); @@ -2824,11 +2826,11 @@ void QFontDatabase::load(const QFontPrivate *d, int script) QStringList::ConstIterator it = family_list.constBegin(), end = family_list.constEnd(); for (; !fe && it != end; ++it) { - req.family = *it; + req.families = QStringList(*it); fe = QFontDatabase::findFont(req, script); if (fe) { - if (fe->type() == QFontEngine::Box && !req.family.isEmpty()) { + if (fe->type() == QFontEngine::Box && !req.families.isEmpty()) { if (fe->ref.load() == 0) delete fe; fe = 0; diff --git a/src/gui/text/qfontengine.cpp b/src/gui/text/qfontengine.cpp index 9b0b0ec0d5..b06b64ca63 100644 --- a/src/gui/text/qfontengine.cpp +++ b/src/gui/text/qfontengine.cpp @@ -1842,7 +1842,7 @@ QFontEngine *QFontEngineMulti::loadEngine(int at) { QFontDef request(fontDef); request.styleStrategy |= QFont::NoFontMerging; - request.family = fallbackFamilyAt(at - 1); + request.families = QStringList(fallbackFamilyAt(at - 1)); // At this point, the main script of the text has already been considered // when fetching the list of fallback families from the database, and the diff --git a/src/gui/text/qtextformat.cpp b/src/gui/text/qtextformat.cpp index 4957da1908..18de8408b5 100644 --- a/src/gui/text/qtextformat.cpp +++ b/src/gui/text/qtextformat.cpp @@ -361,6 +361,9 @@ void QTextFormatPrivate::recalcFont() const case QTextFormat::FontFamily: f.setFamily(props.at(i).value.toString()); break; + case QTextFormat::FontFamilies: + f.setFamilies(props.at(i).value.toStringList()); + break; case QTextFormat::FontPointSize: f.setPointSizeF(props.at(i).value.toReal()); break; @@ -562,6 +565,7 @@ Q_GUI_EXPORT QDataStream &operator>>(QDataStream &stream, QTextFormat &fmt) Character properties \value FontFamily + \value FontFamilies \value FontPointSize \value FontPixelSize \value FontSizeAdjustment Specifies the change in size given to the fontsize already set using @@ -1390,7 +1394,23 @@ QTextCharFormat::QTextCharFormat(const QTextFormat &fmt) \sa font() */ +/*! + \fn void QTextCharFormat::setFontFamilies(const QStringList &families) + \since 5.13 + + Sets the text format's font \a families. + + \sa setFont() +*/ + +/*! + \fn QStringList QTextCharFormat::fontFamilies() const + \since 5.13 + + Returns the text format's font families. + \sa font() +*/ /*! \fn void QTextCharFormat::setFontPointSize(qreal size) @@ -1919,6 +1939,9 @@ void QTextCharFormat::setFont(const QFont &font, FontPropertiesInheritanceBehavi if (mask & QFont::FamilyResolved) setFontFamily(font.family()); + if (mask & QFont::FamiliesResolved) + setFontFamilies(font.families()); + if (mask & QFont::SizeResolved) { const qreal pointSize = font.pointSizeF(); if (pointSize > 0) { diff --git a/src/gui/text/qtextformat.h b/src/gui/text/qtextformat.h index a8e573d5a4..d27d2e7f17 100644 --- a/src/gui/text/qtextformat.h +++ b/src/gui/text/qtextformat.h @@ -188,6 +188,7 @@ public: FontStyleStrategy = 0x1FE4, FontKerning = 0x1FE5, FontHintingPreference = 0x1FE6, + FontFamilies = 0x1FE7, FontFamily = 0x2000, FontPointSize = 0x2001, FontSizeAdjustment = 0x2002, @@ -428,6 +429,11 @@ public: inline QString fontFamily() const { return stringProperty(FontFamily); } + inline void setFontFamilies(const QStringList &families) + { setProperty(FontFamilies, QVariant(families)); } + inline QVariant fontFamilies() const + { return property(FontFamilies); } + inline void setFontPointSize(qreal size) { setProperty(FontPointSize, size); } inline qreal fontPointSize() const diff --git a/src/gui/text/qtexthtmlparser.cpp b/src/gui/text/qtexthtmlparser.cpp index 4be800b251..9d2a5d553a 100644 --- a/src/gui/text/qtexthtmlparser.cpp +++ b/src/gui/text/qtexthtmlparser.cpp @@ -862,7 +862,8 @@ QString QTextHtmlParser::parseWord() ++pos; while (pos < len) { QChar c = txt.at(pos++); - if (c == QLatin1Char('\'')) + // Allow for escaped single quotes as they may be part of the string + if (c == QLatin1Char('\'') && (txt.length() > 1 && txt.at(pos - 2) != QLatin1Char('\\'))) break; else word += c; -- cgit v1.2.3