From af875e88f46a2947c313bd724ab9b10f3e7268df Mon Sep 17 00:00:00 2001 From: Niklas Wenzel Date: Thu, 10 Feb 2022 09:58:11 +0100 Subject: [PATCH] FreeType: Load multiple font faces from the same file on macOS Previously, if a font file contained multiple font faces, only the first could ever be loaded. This could lead to Qt loading different font styles than requested by the application. [ChangeLog][Text][Freetype] Fixed a bug where the macOS FreeType backend would fail to load font faces from font files containing multiple faces. Fixes: QTBUG-100666 Pick-to: 6.2 6.3 Change-Id: I6a126266a2e15f843dd578ab25c11748881bb932 Reviewed-by: Eskil Abrahamsen Blomfeldt --- .../text/coretext/qcoretextfontdatabase.mm | 10 ++- src/gui/text/freetype/qfontengine_ft.cpp | 67 +++++++++++++++++++ src/gui/text/freetype/qfontengine_ft_p.h | 2 + 3 files changed, 77 insertions(+), 2 deletions(-) diff --git a/src/gui/text/coretext/qcoretextfontdatabase.mm b/src/gui/text/coretext/qcoretextfontdatabase.mm index 2c49876895..574f57047e 100644 --- a/src/gui/text/coretext/qcoretextfontdatabase.mm +++ b/src/gui/text/coretext/qcoretextfontdatabase.mm @@ -403,9 +403,15 @@ QFontEngine *QCoreTextFontDatabaseEngineFactory::fontEngine(const return QFontEngineFT::create(*fontData, fontDef.pixelSize, static_cast(fontDef.hintingPreference)); } else if (NSURL *url = descriptorAttribute(descriptor, kCTFontURLAttribute)) { - Q_ASSERT(url.fileURL); QFontEngine::FaceId faceId; - faceId.filename = QString::fromNSString(url.path).toUtf8(); + + Q_ASSERT(url.fileURL); + QString faceFileName{QString::fromNSString(url.path)}; + faceId.filename = faceFileName.toUtf8(); + + QString styleName = QCFString(CTFontDescriptorCopyAttribute(descriptor, kCTFontStyleNameAttribute)); + faceId.index = QFreetypeFace::getFaceIndexByStyleName(faceFileName, styleName); + return QFontEngineFT::create(fontDef, faceId); } // We end up here with a descriptor does not contain Qt font data or kCTFontURLAttribute. diff --git a/src/gui/text/freetype/qfontengine_ft.cpp b/src/gui/text/freetype/qfontengine_ft.cpp index c494c77e95..b4f29cc269 100644 --- a/src/gui/text/freetype/qfontengine_ft.cpp +++ b/src/gui/text/freetype/qfontengine_ft.cpp @@ -128,8 +128,19 @@ public: { } ~QtFreetypeData(); + struct FaceStyle { + QString faceFileName; + QString styleName; + + FaceStyle(QString faceFileName, QString styleName) + : faceFileName(std::move(faceFileName)), + styleName(std::move(styleName)) + {} + }; + FT_Library library; QHash faces; + QHash faceIndices; }; QtFreetypeData::~QtFreetypeData() @@ -141,6 +152,16 @@ QtFreetypeData::~QtFreetypeData() library = nullptr; } +inline bool operator==(const QtFreetypeData::FaceStyle &style1, const QtFreetypeData::FaceStyle &style2) +{ + return style1.faceFileName == style2.faceFileName && style1.styleName == style2.styleName; +} + +inline size_t qHash(const QtFreetypeData::FaceStyle &style, size_t seed) +{ + return qHashMulti(seed, style.faceFileName, style.styleName); +} + Q_GLOBAL_STATIC(QThreadStorage, theFreetypeData) QtFreetypeData *qt_getFreetypeData() @@ -336,6 +357,52 @@ void QFreetypeFace::release(const QFontEngine::FaceId &face_id) } } +static int computeFaceIndex(const QString &faceFileName, const QString &styleName) +{ + FT_Library library = qt_getFreetype(); + + int faceIndex = 0; + int numFaces = 0; + + do { + FT_Face face; + + FT_Error error = FT_New_Face(library, faceFileName.toUtf8().constData(), faceIndex, &face); + if (error != FT_Err_Ok) { + qDebug() << "FT_New_Face failed for face index" << faceIndex << ':' << Qt::hex << error; + break; + } + + QString faceStyleName = QString::fromLatin1(face->style_name); + numFaces = face->num_faces; + + FT_Done_Face(face); + + if (faceStyleName == styleName) + return faceIndex; + } while (++faceIndex < numFaces); + + // Fall back to the first font face in the file + return 0; +} + +int QFreetypeFace::getFaceIndexByStyleName(const QString &faceFileName, const QString &styleName) +{ + QtFreetypeData *freetypeData = qt_getFreetypeData(); + + // Try to get from cache + QtFreetypeData::FaceStyle faceStyle(faceFileName, styleName); + int faceIndex = freetypeData->faceIndices.value(faceStyle, -1); + + if (faceIndex >= 0) + return faceIndex; + + faceIndex = computeFaceIndex(faceFileName, styleName); + + freetypeData->faceIndices.insert(faceStyle, faceIndex); + + return faceIndex; +} void QFreetypeFace::computeSize(const QFontDef &fontDef, int *xsize, int *ysize, bool *outline_drawing, QFixed *scalableBitmapScaleFactor) { diff --git a/src/gui/text/freetype/qfontengine_ft_p.h b/src/gui/text/freetype/qfontengine_ft_p.h index f3ae6d8cad..8c42b99c3b 100644 --- a/src/gui/text/freetype/qfontengine_ft_p.h +++ b/src/gui/text/freetype/qfontengine_ft_p.h @@ -85,6 +85,8 @@ public: const QByteArray &fontData = QByteArray()); void release(const QFontEngine::FaceId &face_id); + static int getFaceIndexByStyleName(const QString &faceFileName, const QString &styleName); + // locks the struct for usage. Any read/write operations require locking. void lock() {