diff options
Diffstat (limited to 'src/gui/text/freetype')
-rw-r--r-- | src/gui/text/freetype/freetype.pri | 9 | ||||
-rw-r--r-- | src/gui/text/freetype/qfontengine_ft.cpp | 446 | ||||
-rw-r--r-- | src/gui/text/freetype/qfontengine_ft_p.h | 122 | ||||
-rw-r--r-- | src/gui/text/freetype/qfreetypefontdatabase.cpp | 239 | ||||
-rw-r--r-- | src/gui/text/freetype/qfreetypefontdatabase_p.h | 55 |
5 files changed, 587 insertions, 284 deletions
diff --git a/src/gui/text/freetype/freetype.pri b/src/gui/text/freetype/freetype.pri deleted file mode 100644 index 7bda687ef4..0000000000 --- a/src/gui/text/freetype/freetype.pri +++ /dev/null @@ -1,9 +0,0 @@ -HEADERS += \ - $$PWD/qfreetypefontdatabase_p.h \ - $$PWD/qfontengine_ft_p.h - -SOURCES += \ - $$PWD/qfreetypefontdatabase.cpp \ - $$PWD/qfontengine_ft.cpp - -QMAKE_USE_PRIVATE += freetype diff --git a/src/gui/text/freetype/qfontengine_ft.cpp b/src/gui/text/freetype/qfontengine_ft.cpp index 42cf147901..d3791f1f6e 100644 --- a/src/gui/text/freetype/qfontengine_ft.cpp +++ b/src/gui/text/freetype/qfontengine_ft.cpp @@ -1,41 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QtGui module of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2021 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 "qdir.h" #include "qmetatype.h" @@ -48,6 +12,7 @@ #include <qscreen.h> #include <qpa/qplatformscreen.h> #include <QtCore/QUuid> +#include <QtCore/QLoggingCategory> #include <QtGui/QPainterPath> #ifndef QT_NO_FREETYPE @@ -59,6 +24,8 @@ #include <qmath.h> #include <qendian.h> +#include <memory> + #include <ft2build.h> #include FT_FREETYPE_H #include FT_OUTLINE_H @@ -68,6 +35,7 @@ #include FT_GLYPH_H #include FT_MODULE_H #include FT_LCD_FILTER_H +#include FT_MULTIPLE_MASTERS_H #if defined(FT_CONFIG_OPTIONS_H) #include FT_CONFIG_OPTIONS_H @@ -87,6 +55,10 @@ QT_BEGIN_NAMESPACE +Q_DECLARE_LOGGING_CATEGORY(lcFontMatch) + +using namespace Qt::StringLiterals; + #define FLOOR(x) ((x) & -64) #define CEIL(x) (((x)+63) & -64) #define TRUNC(x) ((x) >> 6) @@ -126,19 +98,43 @@ 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<QFontEngine::FaceId, QFreetypeFace *> faces; + QHash<FaceStyle, int> faceIndices; }; QtFreetypeData::~QtFreetypeData() { - for (QHash<QFontEngine::FaceId, QFreetypeFace *>::ConstIterator iter = faces.cbegin(); iter != faces.cend(); ++iter) + for (auto iter = faces.cbegin(); iter != faces.cend(); ++iter) { iter.value()->cleanup(); + if (!iter.value()->ref.deref()) + delete iter.value(); + } faces.clear(); FT_Done_FreeType(library); 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<QtFreetypeData *>, theFreetypeData) QtFreetypeData *qt_getFreetypeData() @@ -194,10 +190,15 @@ int QFreetypeFace::getPointInOutline(glyph_t glyph, int flags, quint32 point, QF return Err_Ok; } +bool QFreetypeFace::isScalable() const +{ + return FT_IS_SCALABLE(face); +} + bool QFreetypeFace::isScalableBitmap() const { #ifdef FT_HAS_COLOR - return !FT_IS_SCALABLE(face) && FT_HAS_COLOR(face); + return !isScalable() && FT_HAS_COLOR(face); #else return false; #endif @@ -220,18 +221,37 @@ QFreetypeFace *QFreetypeFace::getFace(const QFontEngine::FaceId &face_id, QtFreetypeData *freetypeData = qt_getFreetypeData(); - QFreetypeFace *freetype = freetypeData->faces.value(face_id, nullptr); - if (freetype) { - freetype->ref.ref(); - } else { - QScopedPointer<QFreetypeFace> newFreetype(new QFreetypeFace); + QFreetypeFace *freetype = nullptr; + auto it = freetypeData->faces.find(face_id); + if (it != freetypeData->faces.end()) { + freetype = *it; + + Q_ASSERT(freetype->ref.loadRelaxed() > 0); + if (freetype->ref.loadRelaxed() == 1) { + // If there is only one reference left to the face, it means it is only referenced by + // the cache itself, and thus it is in cleanup state (but the final outside reference + // was removed on a different thread so it could not be deleted right away). We then + // complete the cleanup and pretend we didn't find it, so that it can be re-created with + // the present state. + freetype->cleanup(); + freetypeData->faces.erase(it); + delete freetype; + freetype = nullptr; + } else { + freetype->ref.ref(); + } + } + + if (!freetype) { + const auto deleter = [](QFreetypeFace *f) { delete f; }; + std::unique_ptr<QFreetypeFace, decltype(deleter)> newFreetype(new QFreetypeFace, deleter); FT_Face face; if (!face_id.filename.isEmpty()) { QString fileName = QFile::decodeName(face_id.filename); - if (face_id.filename.startsWith(":qmemoryfonts/")) { + if (const char *prefix = ":qmemoryfonts/"; face_id.filename.startsWith(prefix)) { // from qfontdatabase.cpp QByteArray idx = face_id.filename; - idx.remove(0, 14); // remove ':qmemoryfonts/' + idx.remove(0, strlen(prefix)); // remove ':qmemoryfonts/' bool ok = false; newFreetype->fontData = qt_fontdata_from_index(idx.toInt(&ok)); if (!ok) @@ -253,7 +273,23 @@ QFreetypeFace *QFreetypeFace::getFace(const QFontEngine::FaceId &face_id, } else if (FT_New_Face(freetypeData->library, face_id.filename, face_id.index, &face)) { return nullptr; } + +#if (FREETYPE_MAJOR*10000 + FREETYPE_MINOR*100 + FREETYPE_PATCH) >= 20900 + if (face_id.instanceIndex >= 0) { + qCDebug(lcFontMatch) + << "Selecting named instance" << (face_id.instanceIndex) + << "in" << face_id.filename; + FT_Set_Named_Instance(face, face_id.instanceIndex + 1); + } +#endif newFreetype->face = face; + newFreetype->mm_var = nullptr; + if (FT_IS_NAMED_INSTANCE(newFreetype->face)) { + FT_Error ftresult; + ftresult = FT_Get_MM_Var(face, &newFreetype->mm_var); + if (ftresult != FT_Err_Ok) + newFreetype->mm_var = nullptr; + } newFreetype->ref.storeRelaxed(1); newFreetype->xsize = 0; @@ -292,14 +328,34 @@ QFreetypeFace *QFreetypeFace::getFace(const QFontEngine::FaceId &face_id, FT_Set_Char_Size(face, newFreetype->face->available_sizes[0].x_ppem, newFreetype->face->available_sizes[0].y_ppem, 0, 0); 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.data()); + freetypeData->faces.insert(face_id, newFreetype.get()); } QT_CATCH(...) { - newFreetype.take()->release(face_id); + newFreetype.release()->release(face_id); // we could return null in principle instead of throwing QT_RETHROW; } - freetype = newFreetype.take(); + freetype = newFreetype.release(); + freetype->ref.ref(); } return freetype; } @@ -307,32 +363,93 @@ 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); + mm_var = nullptr; FT_Done_Face(face); face = nullptr; } void QFreetypeFace::release(const QFontEngine::FaceId &face_id) { - if (!ref.deref()) { - if (face) { - QtFreetypeData *freetypeData = qt_getFreetypeData(); + Q_UNUSED(face_id); + bool deleteThis = !ref.deref(); + + // If the only reference left over is the cache's reference, we remove it from the cache, + // granted that we are on the correct thread. If not, we leave it there to be cleaned out + // later. While we are at it, we also purge all left over faces which are only referenced + // from the cache. + if (face && ref.loadRelaxed() == 1) { + QtFreetypeData *freetypeData = qt_getFreetypeData(); + for (auto it = freetypeData->faces.constBegin(); it != freetypeData->faces.constEnd(); ) { + if (it.value()->ref.loadRelaxed() == 1) { + it.value()->cleanup(); + if (it.value() == this) + deleteThis = true; // This face, delete at end of function for safety + else + delete it.value(); + it = freetypeData->faces.erase(it); + } else { + ++it; + } + } - cleanup(); + if (freetypeData->faces.isEmpty()) { + FT_Done_FreeType(freetypeData->library); + freetypeData->library = nullptr; + } + } - auto it = freetypeData->faces.constFind(face_id); - if (it != freetypeData->faces.constEnd()) - freetypeData->faces.erase(it); + if (deleteThis) + delete this; +} - if (freetypeData->faces.isEmpty()) { - FT_Done_FreeType(freetypeData->library); - freetypeData->library = nullptr; - } +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; } - delete this; - } + const bool found = QLatin1StringView(face->style_name) == styleName; + numFaces = face->num_faces; + + FT_Done_Face(face); + + if (found) + 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) { @@ -594,7 +711,7 @@ static QFontEngine::SubpixelAntialiasingType subpixelAntialiasingTypeHint() QFontEngineFT *QFontEngineFT::create(const QFontDef &fontDef, FaceId faceId, const QByteArray &fontData) { - QScopedPointer<QFontEngineFT> engine(new QFontEngineFT(fontDef)); + auto engine = std::make_unique<QFontEngineFT>(fontDef); QFontEngineFT::GlyphFormat format = QFontEngineFT::Format_Mono; const bool antialias = !(fontDef.styleStrategy & QFont::NoAntialias); @@ -616,7 +733,7 @@ QFontEngineFT *QFontEngineFT::create(const QFontDef &fontDef, FaceId faceId, con } engine->setQtDefaultHintStyle(static_cast<QFont::HintingPreference>(fontDef.hintingPreference)); - return engine.take(); + return engine.release(); } namespace { @@ -629,7 +746,7 @@ namespace { void updateFamilyNameAndStyle() { - fontDef.family = QString::fromLatin1(freetype->face->family_name); + fontDef.families = QStringList(QString::fromLatin1(freetype->face->family_name)); if (freetype->face->style_flags & FT_STYLE_FLAG_ITALIC) fontDef.style = QFont::StyleItalic; @@ -638,27 +755,32 @@ namespace { fontDef.weight = QFont::Bold; } - bool initFromData(const QByteArray &fontData) + bool initFromData(const QByteArray &fontData, const QMap<QFont::Tag, float> &variableAxisValues) { FaceId faceId; faceId.filename = ""; faceId.index = 0; faceId.uuid = QUuid::createUuid().toByteArray(); + faceId.variableAxes = variableAxisValues; return init(faceId, true, Format_None, fontData); } }; } -QFontEngineFT *QFontEngineFT::create(const QByteArray &fontData, qreal pixelSize, QFont::HintingPreference hintingPreference) +QFontEngineFT *QFontEngineFT::create(const QByteArray &fontData, + qreal pixelSize, + QFont::HintingPreference hintingPreference, + const QMap<QFont::Tag, float> &variableAxisValues) { QFontDef fontDef; fontDef.pixelSize = pixelSize; fontDef.stretch = QFont::Unstretched; fontDef.hintingPreference = hintingPreference; + fontDef.variableAxisValues = variableAxisValues; QFontEngineFTRawData *fe = new QFontEngineFTRawData(fontDef); - if (!fe->initFromData(fontData)) { + if (!fe->initFromData(fontData, variableAxisValues)) { delete fe; return nullptr; } @@ -711,6 +833,37 @@ bool QFontEngineFT::init(FaceId faceId, bool antialias, GlyphFormat format, static void dont_delete(void*) {} +static FT_UShort calculateActualWeight(QFreetypeFace *freetypeFace, FT_Face face, QFontEngine::FaceId faceId) +{ + FT_MM_Var *var = freetypeFace->mm_var; + if (var != nullptr && faceId.instanceIndex >= 0 && FT_UInt(faceId.instanceIndex) < var->num_namedstyles) { + for (FT_UInt axis = 0; axis < var->num_axis; ++axis) { + if (var->axis[axis].tag == QFont::Tag("wght").value()) { + return var->namedstyle[faceId.instanceIndex].coords[axis] >> 16; + } + } + } + if (const TT_OS2 *os2 = reinterpret_cast<const TT_OS2 *>(FT_Get_Sfnt_Table(face, ft_sfnt_os2))) { + return os2->usWeightClass; + } + + return 700; +} + +static bool calculateActualItalic(QFreetypeFace *freetypeFace, FT_Face face, QFontEngine::FaceId faceId) +{ + FT_MM_Var *var = freetypeFace->mm_var; + if (var != nullptr && faceId.instanceIndex >= 0 && FT_UInt(faceId.instanceIndex) < var->num_namedstyles) { + for (FT_UInt axis = 0; axis < var->num_axis; ++axis) { + if (var->axis[axis].tag == QFont::Tag("ital").value()) { + return (var->namedstyle[faceId.instanceIndex].coords[axis] >> 16) == 1; + } + } + } + + return (face->style_flags & FT_STYLE_FLAG_ITALIC); +} + bool QFontEngineFT::init(FaceId faceId, bool antialias, GlyphFormat format, QFreetypeFace *freetypeFace) { @@ -734,7 +887,7 @@ bool QFontEngineFT::init(FaceId faceId, bool antialias, GlyphFormat format, PS_FontInfoRec psrec; // don't assume that type1 fonts are symbol fonts by default if (FT_Get_PS_Font_Info(freetype->face, &psrec) == FT_Err_Ok) { - symbol = bool(fontDef.family.contains(QLatin1String("symbol"), Qt::CaseInsensitive)); + symbol = !fontDef.families.isEmpty() && bool(fontDef.families.constFirst().contains("symbol"_L1, Qt::CaseInsensitive)); } freetype->computeSize(fontDef, &xsize, &ysize, &defaultGlyphSet.outline_drawing, &scalableBitmapScaleFactor); @@ -742,34 +895,36 @@ bool QFontEngineFT::init(FaceId faceId, bool antialias, GlyphFormat format, FT_Face face = lockFace(); if (FT_IS_SCALABLE(face)) { - bool fake_oblique = (fontDef.style != QFont::StyleNormal) && !(face->style_flags & FT_STYLE_FLAG_ITALIC) && !qEnvironmentVariableIsSet("QT_NO_SYNTHESIZED_ITALIC"); + bool isItalic = calculateActualItalic(freetype, face, faceId); + bool fake_oblique = (fontDef.style != QFont::StyleNormal) && !isItalic && !qEnvironmentVariableIsSet("QT_NO_SYNTHESIZED_ITALIC"); if (fake_oblique) obliquen = true; FT_Set_Transform(face, &matrix, nullptr); freetype->matrix = matrix; // fake bold if ((fontDef.weight >= QFont::Bold) && !(face->style_flags & FT_STYLE_FLAG_BOLD) && !FT_IS_FIXED_WIDTH(face) && !qEnvironmentVariableIsSet("QT_NO_SYNTHESIZED_BOLD")) { - if (const TT_OS2 *os2 = reinterpret_cast<const TT_OS2 *>(FT_Get_Sfnt_Table(face, ft_sfnt_os2))) { - if (os2->usWeightClass < 700 && fontDef.pixelSize < 64) - embolden = true; + FT_UShort actualWeight = calculateActualWeight(freetype, face, faceId); + if (actualWeight < 700 && + (fontDef.pixelSize < 64 || qEnvironmentVariableIsSet("QT_NO_SYNTHESIZED_BOLD_LIMIT"))) { + embolden = true; } } // underline metrics line_thickness = QFixed::fromFixed(FT_MulFix(face->underline_thickness, face->size->metrics.y_scale)); - underline_position = QFixed::fromFixed(-FT_MulFix(face->underline_position, face->size->metrics.y_scale)); + QFixed center_position = QFixed::fromFixed(-FT_MulFix(face->underline_position, face->size->metrics.y_scale)); + underline_position = center_position - line_thickness / 2; } else { // ad hoc algorithm int score = fontDef.weight * fontDef.pixelSize; - line_thickness = score / 700; + line_thickness = score / 7000; // looks better with thicker line for small pointsizes if (line_thickness < 2 && score >= 1050) line_thickness = 2; underline_position = ((line_thickness * 2) + 3) / 6; - if (isScalableBitmap()) { + cacheEnabled = false; + if (isScalableBitmap()) glyphFormat = defaultFormat = GlyphFormat::Format_ARGB; - cacheEnabled = false; - } } if (line_thickness < 1) line_thickness = 1; @@ -780,7 +935,7 @@ bool QFontEngineFT::init(FaceId faceId, bool antialias, GlyphFormat format, TrueType fonts with embedded bitmaps may have a bitmap font specific ascent/descent in the EBLC table. There is no direct public API to extract those values. The only way we've found is to trick freetype - into thinking that it's not a scalable font in FT_SelectSize so that + into thinking that it's not a scalable font in FT_Select_Size so that the metrics are retrieved from the bitmap strikes. */ if (FT_IS_SCALABLE(face)) { @@ -794,7 +949,7 @@ bool QFontEngineFT::init(FaceId faceId, bool antialias, GlyphFormat format, metrics.ascender = face->size->metrics.ascender; metrics.descender = face->size->metrics.descender; if (metrics.descender > 0 - && QString::fromUtf8(face->family_name) == QLatin1String("Courier New")) { + && QString::fromUtf8(face->family_name) == "Courier New"_L1) { metrics.descender *= -1; } metrics.height = metrics.ascender - metrics.descender + leading; @@ -903,7 +1058,7 @@ int QFontEngineFT::loadFlags(QGlyphSet *set, GlyphFormat format, int flags, static inline bool areMetricsTooLarge(const QFontEngineFT::GlyphInfo &info) { // false if exceeds QFontEngineFT::Glyph metrics - return info.width > 0xFF || info.height > 0xFF; + return info.width > 0xFF || info.height > 0xFF || info.linearAdvance > 0x7FFF; } static inline void transformBoundingBox(int *left, int *top, int *right, int *bottom, FT_Matrix *matrix) @@ -943,7 +1098,7 @@ static inline void transformBoundingBox(int *left, int *top, int *right, int *bo } QFontEngineFT::Glyph *QFontEngineFT::loadGlyph(QGlyphSet *set, uint glyph, - QFixed subPixelPosition, + const QFixedPoint &subPixelPosition, GlyphFormat format, bool fetchMetricsOnly, bool disableOutlineDrawing) const @@ -967,8 +1122,8 @@ QFontEngineFT::Glyph *QFontEngineFT::loadGlyph(QGlyphSet *set, uint glyph, FT_Matrix matrix = freetype->matrix; FT_Vector v; - v.x = format == Format_Mono ? 0 : FT_Pos(subPixelPosition.value()); - v.y = 0; + v.x = format == Format_Mono ? 0 : FT_Pos(subPixelPosition.x.value()); + v.y = format == Format_Mono ? 0 : FT_Pos(-subPixelPosition.y.value()); FT_Set_Transform(face, &matrix, &v); bool hsubpixel = false; @@ -1051,6 +1206,7 @@ QFontEngineFT::Glyph *QFontEngineFT::loadGlyph(QGlyphSet *set, uint glyph, info.height = TRUNC(top - bottom); // If any of the metrics are too large to fit, don't cache them + // Also, avoid integer overflow when linearAdvance is to large to fit in a signed short if (areMetricsTooLarge(info)) return nullptr; @@ -1071,14 +1227,18 @@ QFontEngineFT::Glyph *QFontEngineFT::loadGlyph(QGlyphSet *set, uint glyph, } int glyph_buffer_size = 0; - QScopedArrayPointer<uchar> glyph_buffer; + std::unique_ptr<uchar[]> glyph_buffer; FT_Render_Mode renderMode = (default_hint_style == HintLight) ? FT_RENDER_MODE_LIGHT : FT_RENDER_MODE_NORMAL; switch (format) { case Format_Mono: renderMode = FT_RENDER_MODE_MONO; break; case Format_A32: - Q_ASSERT(hsubpixel || vfactor != 1); + if (!hsubpixel && vfactor == 1) { + qWarning("Format_A32 requested, but subpixel layout is unknown."); + return nullptr; + } + renderMode = hsubpixel ? FT_RENDER_MODE_LCD : FT_RENDER_MODE_LCD_V; break; case Format_A8: @@ -1112,7 +1272,7 @@ QFontEngineFT::Glyph *QFontEngineFT::loadGlyph(QGlyphSet *set, uint glyph, if (slot->bitmap.pixel_mode == FT_PIXEL_MODE_MONO) { uchar *src = slot->bitmap.buffer; - uchar *dst = glyph_buffer.data(); + uchar *dst = glyph_buffer.get(); int h = slot->bitmap.rows; // Some fonts return bitmaps even when we requested something else: if (format == Format_Mono) { @@ -1141,7 +1301,7 @@ QFontEngineFT::Glyph *QFontEngineFT::loadGlyph(QGlyphSet *set, uint glyph, } else if (slot->bitmap.pixel_mode == 7 /*FT_PIXEL_MODE_BGRA*/) { Q_ASSERT(format == Format_ARGB); uchar *src = slot->bitmap.buffer; - uchar *dst = glyph_buffer.data(); + uchar *dst = glyph_buffer.get(); int h = slot->bitmap.rows; while (h--) { #if Q_BYTE_ORDER == Q_BIG_ENDIAN @@ -1161,7 +1321,7 @@ QFontEngineFT::Glyph *QFontEngineFT::loadGlyph(QGlyphSet *set, uint glyph, } else if (slot->bitmap.pixel_mode == FT_PIXEL_MODE_GRAY) { Q_ASSERT(format == Format_A8); uchar *src = slot->bitmap.buffer; - uchar *dst = glyph_buffer.data(); + uchar *dst = glyph_buffer.get(); int h = slot->bitmap.rows; int bytes = info.width; while (h--) { @@ -1171,10 +1331,10 @@ QFontEngineFT::Glyph *QFontEngineFT::loadGlyph(QGlyphSet *set, uint glyph, } } else if (slot->bitmap.pixel_mode == FT_PIXEL_MODE_LCD) { Q_ASSERT(format == Format_A32); - convertRGBToARGB(slot->bitmap.buffer, (uint *)glyph_buffer.data(), info.width, info.height, slot->bitmap.pitch, subpixelType != Subpixel_RGB); + convertRGBToARGB(slot->bitmap.buffer, (uint *)glyph_buffer.get(), info.width, info.height, slot->bitmap.pitch, subpixelType != Subpixel_RGB); } else if (slot->bitmap.pixel_mode == FT_PIXEL_MODE_LCD_V) { Q_ASSERT(format == Format_A32); - convertRGBToARGB_V(slot->bitmap.buffer, (uint *)glyph_buffer.data(), info.width, info.height, slot->bitmap.pitch, subpixelType != Subpixel_VRGB); + convertRGBToARGB_V(slot->bitmap.buffer, (uint *)glyph_buffer.get(), info.width, info.height, slot->bitmap.pitch, subpixelType != Subpixel_VRGB); } else { qWarning("QFontEngine: Glyph rendered in unknown pixel_mode=%d", slot->bitmap.pixel_mode); return nullptr; @@ -1193,7 +1353,7 @@ QFontEngineFT::Glyph *QFontEngineFT::loadGlyph(QGlyphSet *set, uint glyph, g->advance = info.xOff; g->format = format; delete [] g->data; - g->data = glyph_buffer.take(); + g->data = glyph_buffer.release(); if (set) set->setGlyph(glyph, subPixelPosition, g); @@ -1210,7 +1370,7 @@ QFontEngine::Properties QFontEngineFT::properties() const { Properties p = freetype->properties(); if (p.postscriptName.isEmpty()) { - p.postscriptName = QFontEngine::convertToPostscriptFontFamilyName(fontDef.family.toUtf8()); + p.postscriptName = QFontEngine::convertToPostscriptFontFamilyName(fontDef.families.constFirst().toUtf8()); } return freetype->properties(); @@ -1437,7 +1597,7 @@ void QFontEngineFT::getUnscaledGlyph(glyph_t glyph, QPainterPath *path, glyph_me bool QFontEngineFT::supportsTransformation(const QTransform &transform) const { - return transform.type() <= QTransform::TxRotate; + return transform.type() <= QTransform::TxRotate && (freetype->isScalable() || freetype->isScalableBitmap()); } void QFontEngineFT::addOutlineToPath(qreal x, qreal y, const QGlyphLayout &glyphs, QPainterPath *path, QTextItem::RenderFlags flags) @@ -1518,15 +1678,16 @@ glyph_t QFontEngineFT::glyphIndex(uint ucs4) const return glyph; } -bool QFontEngineFT::stringToCMap(const QChar *str, int len, QGlyphLayout *glyphs, int *nglyphs, +int QFontEngineFT::stringToCMap(const QChar *str, int len, QGlyphLayout *glyphs, int *nglyphs, QFontEngine::ShaperFlags flags) const { Q_ASSERT(glyphs->numGlyphs >= *nglyphs); if (*nglyphs < len) { *nglyphs = len; - return false; + return -1; } + int mappedGlyphs = 0; int glyph_pos = 0; if (freetype->symbol_map) { FT_Face face = freetype->face; @@ -1559,6 +1720,8 @@ bool QFontEngineFT::stringToCMap(const QChar *str, int len, QGlyphLayout *glyphs if (uc < QFreetypeFace::cmapCacheSize) freetype->cmapCache[uc] = glyph; } + if (glyphs->glyphs[glyph_pos] || isIgnorableChar(uc)) + mappedGlyphs++; ++glyph_pos; } } else { @@ -1580,6 +1743,8 @@ bool QFontEngineFT::stringToCMap(const QChar *str, int len, QGlyphLayout *glyphs freetype->cmapCache[uc] = glyph; } } + if (glyphs->glyphs[glyph_pos] || isIgnorableChar(uc)) + mappedGlyphs++; ++glyph_pos; } } @@ -1590,7 +1755,7 @@ bool QFontEngineFT::stringToCMap(const QChar *str, int len, QGlyphLayout *glyphs if (!(flags & GlyphIndicesOnly)) recalcAdvances(glyphs, flags); - return true; + return mappedGlyphs; } bool QFontEngineFT::shouldUseDesignMetrics(QFontEngine::ShaperFlags flags) const @@ -1644,7 +1809,11 @@ void QFontEngineFT::recalcAdvances(QGlyphLayout *glyphs, QFontEngine::ShaperFlag } else { if (!face) face = lockFace(); - g = loadGlyph(cacheEnabled ? &defaultGlyphSet : nullptr, glyphs->glyphs[i], 0, Format_None, true); + g = loadGlyph(cacheEnabled ? &defaultGlyphSet : nullptr, + glyphs->glyphs[i], + QFixedPoint(), + Format_None, + true); if (g) glyphs->advances[i] = design ? QFixed::fromFixed(g->linearAdvance) : QFixed(g->advance); else @@ -1685,16 +1854,19 @@ glyph_metrics_t QFontEngineFT::boundingBox(const QGlyphLayout &glyphs) if (!g) { if (!face) face = lockFace(); - g = loadGlyph(cacheEnabled ? &defaultGlyphSet : nullptr, glyphs.glyphs[i], 0, Format_None, true); + g = loadGlyph(cacheEnabled ? &defaultGlyphSet : nullptr, + glyphs.glyphs[i], + QFixedPoint(), + Format_None, + true); } if (g) { QFixed x = overall.xoff + glyphs.offsets[i].x + g->x; QFixed y = overall.yoff + glyphs.offsets[i].y - g->y; overall.x = qMin(overall.x, x); overall.y = qMin(overall.y, y); - xmax = qMax(xmax, x + g->width); - ymax = qMax(ymax, y + g->height); - overall.xoff += g->advance; + xmax = qMax(xmax, x.ceil() + g->width); + ymax = qMax(ymax, y.ceil() + g->height); if (!cacheEnabled && g != &emptyGlyph) delete g; } else { @@ -1709,8 +1881,8 @@ glyph_metrics_t QFontEngineFT::boundingBox(const QGlyphLayout &glyphs) overall.y = qMin(overall.y, y); xmax = qMax(xmax, x + TRUNC(right - left)); ymax = qMax(ymax, y + TRUNC(top - bottom)); - overall.xoff += int(TRUNC(ROUND(face->glyph->advance.x))); } + overall.xoff += glyphs.effectiveAdvance(i); } overall.height = qMax(overall.height, ymax - overall.y); overall.width = xmax - overall.x; @@ -1730,7 +1902,11 @@ glyph_metrics_t QFontEngineFT::boundingBox(glyph_t glyph) Glyph *g = cacheEnabled ? defaultGlyphSet.getGlyph(glyph) : nullptr; if (!g) { face = lockFace(); - g = loadGlyph(cacheEnabled ? &defaultGlyphSet : nullptr, glyph, 0, Format_None, true); + g = loadGlyph(cacheEnabled ? &defaultGlyphSet : nullptr, + glyph, + QFixedPoint(), + Format_None, + true); } if (g) { overall.x = g->x; @@ -1762,12 +1938,23 @@ glyph_metrics_t QFontEngineFT::boundingBox(glyph_t glyph) glyph_metrics_t QFontEngineFT::boundingBox(glyph_t glyph, const QTransform &matrix) { - return alphaMapBoundingBox(glyph, 0, matrix, QFontEngine::Format_None); + return alphaMapBoundingBox(glyph, QFixedPoint(), matrix, QFontEngine::Format_None); } -glyph_metrics_t QFontEngineFT::alphaMapBoundingBox(glyph_t glyph, QFixed subPixelPosition, const QTransform &matrix, QFontEngine::GlyphFormat format) +glyph_metrics_t QFontEngineFT::alphaMapBoundingBox(glyph_t glyph, + const QFixedPoint &subPixelPosition, + const QTransform &matrix, + QFontEngine::GlyphFormat format) { - Glyph *g = loadGlyphFor(glyph, subPixelPosition, format, matrix, true); + // When rendering glyphs into a cache via the alphaMap* functions, we disable + // outline drawing. To ensure the bounding box matches the rendered glyph, we + // need to do the same here. + + const bool needsImageTransform = !FT_IS_SCALABLE(freetype->face) + && matrix.type() > QTransform::TxTranslate; + if (needsImageTransform && format == QFontEngine::Format_Mono) + format = QFontEngine::Format_A8; + Glyph *g = loadGlyphFor(glyph, subPixelPosition, format, matrix, true, true); glyph_metrics_t overall; if (g) { @@ -1793,7 +1980,7 @@ glyph_metrics_t QFontEngineFT::alphaMapBoundingBox(glyph_t glyph, QFixed subPixe unlockFace(); } - if (isScalableBitmap()) + if (isScalableBitmap() || needsImageTransform) overall = scaledBitmapMetrics(overall, matrix); return overall; } @@ -1828,8 +2015,10 @@ static inline QImage alphaMapFromGlyphData(QFontEngineFT::Glyph *glyph, QFontEng return img; } -QFontEngine::Glyph *QFontEngineFT::glyphData(glyph_t glyphIndex, QFixed subPixelPosition, - QFontEngine::GlyphFormat neededFormat, const QTransform &t) +QFontEngine::Glyph *QFontEngineFT::glyphData(glyph_t glyphIndex, + const QFixedPoint &subPixelPosition, + QFontEngine::GlyphFormat neededFormat, + const QTransform &t) { Q_ASSERT(cacheEnabled); @@ -1854,7 +2043,7 @@ static inline bool is2dRotation(const QTransform &t) } QFontEngineFT::Glyph *QFontEngineFT::loadGlyphFor(glyph_t g, - QFixed subPixelPosition, + const QFixedPoint &subPixelPosition, GlyphFormat format, const QTransform &t, bool fetchBoundingBox, @@ -1882,19 +2071,26 @@ QFontEngineFT::Glyph *QFontEngineFT::loadGlyphFor(glyph_t g, return glyph; } -QImage QFontEngineFT::alphaMapForGlyph(glyph_t g, QFixed subPixelPosition) +QImage QFontEngineFT::alphaMapForGlyph(glyph_t g, const QFixedPoint &subPixelPosition) { return alphaMapForGlyph(g, subPixelPosition, QTransform()); } -QImage QFontEngineFT::alphaMapForGlyph(glyph_t g, QFixed subPixelPosition, const QTransform &t) +QImage QFontEngineFT::alphaMapForGlyph(glyph_t g, + const QFixedPoint &subPixelPosition, + const QTransform &t) { - const GlyphFormat neededFormat = antialias ? Format_A8 : Format_Mono; + const bool needsImageTransform = !FT_IS_SCALABLE(freetype->face) + && t.type() > QTransform::TxTranslate; + const GlyphFormat neededFormat = antialias || needsImageTransform ? Format_A8 : Format_Mono; Glyph *glyph = loadGlyphFor(g, subPixelPosition, neededFormat, t, false, true); QImage img = alphaMapFromGlyphData(glyph, neededFormat); - img = img.copy(); + if (needsImageTransform) + img = img.transformed(t, Qt::SmoothTransformation); + else + img = img.copy(); if (!cacheEnabled && glyph != &emptyGlyph) delete glyph; @@ -1902,7 +2098,9 @@ QImage QFontEngineFT::alphaMapForGlyph(glyph_t g, QFixed subPixelPosition, const return img; } -QImage QFontEngineFT::alphaRGBMapForGlyph(glyph_t g, QFixed subPixelPosition, const QTransform &t) +QImage QFontEngineFT::alphaRGBMapForGlyph(glyph_t g, + const QFixedPoint &subPixelPosition, + const QTransform &t) { if (t.type() > QTransform::TxRotate) return QFontEngine::alphaRGBMapForGlyph(g, subPixelPosition, t); @@ -1923,7 +2121,10 @@ QImage QFontEngineFT::alphaRGBMapForGlyph(glyph_t g, QFixed subPixelPosition, co return QFontEngine::alphaRGBMapForGlyph(g, subPixelPosition, t); } -QImage QFontEngineFT::bitmapForGlyph(glyph_t g, QFixed subPixelPosition, const QTransform &t, const QColor &color) +QImage QFontEngineFT::bitmapForGlyph(glyph_t g, + const QFixedPoint &subPixelPosition, + const QTransform &t, + const QColor &color) { Q_UNUSED(color); @@ -1952,7 +2153,7 @@ QImage QFontEngineFT::bitmapForGlyph(glyph_t g, QFixed subPixelPosition, const Q void QFontEngineFT::removeGlyphFromCache(glyph_t glyph) { - defaultGlyphSet.removeGlyphFromCache(glyph, 0); + defaultGlyphSet.removeGlyphFromCache(glyph, QFixedPoint()); } int QFontEngineFT::glyphCount() const @@ -2033,7 +2234,8 @@ void QFontEngineFT::QGlyphSet::clear() glyph_data.clear(); } -void QFontEngineFT::QGlyphSet::removeGlyphFromCache(glyph_t index, QFixed subPixelPosition) +void QFontEngineFT::QGlyphSet::removeGlyphFromCache(glyph_t index, + const QFixedPoint &subPixelPosition) { if (useFastGlyphData(index, subPixelPosition)) { if (fast_glyph_data[index]) { @@ -2047,7 +2249,9 @@ void QFontEngineFT::QGlyphSet::removeGlyphFromCache(glyph_t index, QFixed subPix } } -void QFontEngineFT::QGlyphSet::setGlyph(glyph_t index, QFixed subPixelPosition, Glyph *glyph) +void QFontEngineFT::QGlyphSet::setGlyph(glyph_t index, + const QFixedPoint &subPixelPosition, + Glyph *glyph) { if (useFastGlyphData(index, subPixelPosition)) { if (!fast_glyph_data[index]) diff --git a/src/gui/text/freetype/qfontengine_ft_p.h b/src/gui/text/freetype/qfontengine_ft_p.h index dcbe49ff99..bdd4549827 100644 --- a/src/gui/text/freetype/qfontengine_ft_p.h +++ b/src/gui/text/freetype/qfontengine_ft_p.h @@ -1,41 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QtGui module of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2021 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 QFONTENGINE_FT_P_H #define QFONTENGINE_FT_P_H // @@ -55,6 +19,7 @@ #include <ft2build.h> #include FT_FREETYPE_H +#include FT_MULTIPLE_MASTERS_H #ifndef Q_OS_WIN @@ -85,6 +50,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() { @@ -96,6 +63,7 @@ public: } FT_Face face; + FT_MM_Var *mm_var; int xsize; // 26.6 int ysize; // 26.6 FT_Matrix matrix; @@ -109,6 +77,7 @@ public: int getPointInOutline(glyph_t glyph, int flags, quint32 point, QFixed *xpos, QFixed *ypos, quint32 *nPoints); + bool isScalable() const; bool isScalableBitmap() const; static void addGlyphToPath(FT_Face face, FT_GlyphSlot g, const QFixedPoint &point, QPainterPath *path, FT_Fixed x_scale, FT_Fixed y_scale); @@ -117,7 +86,6 @@ public: private: friend class QFontEngineFT; friend class QtFreetypeData; - friend struct QScopedPointerDeleter<QFreetypeFace>; QFreetypeFace() = default; ~QFreetypeFace() {} void cleanup(); @@ -143,7 +111,7 @@ public: struct GlyphAndSubPixelPosition { - GlyphAndSubPixelPosition(glyph_t g, QFixed spp) : glyph(g), subPixelPosition(spp) {} + GlyphAndSubPixelPosition(glyph_t g, const QFixedPoint spp) : glyph(g), subPixelPosition(spp) {} bool operator==(const GlyphAndSubPixelPosition &other) const { @@ -151,7 +119,7 @@ public: } glyph_t glyph; - QFixed subPixelPosition; + QFixedPoint subPixelPosition; }; struct QGlyphSet @@ -161,13 +129,14 @@ public: FT_Matrix transformationMatrix; bool outline_drawing; - void removeGlyphFromCache(glyph_t index, QFixed subPixelPosition); + void removeGlyphFromCache(glyph_t index, const QFixedPoint &subPixelPosition); void clear(); - inline bool useFastGlyphData(glyph_t index, QFixed subPixelPosition) const { - return (index < 256 && subPixelPosition == 0); + inline bool useFastGlyphData(glyph_t index, const QFixedPoint &subPixelPosition) const { + return (index < 256 && subPixelPosition.x == 0 && subPixelPosition.y == 0); } - inline Glyph *getGlyph(glyph_t index, QFixed subPixelPosition = 0) const; - void setGlyph(glyph_t index, QFixed spp, Glyph *glyph); + inline Glyph *getGlyph(glyph_t index, + const QFixedPoint &subPixelPositionX = QFixedPoint()) const; + void setGlyph(glyph_t index, const QFixedPoint &spp, Glyph *glyph); inline bool isGlyphMissing(glyph_t index) const { return missing_glyphs.contains(index); } inline void setGlyphMissing(glyph_t index) const { missing_glyphs.insert(index); } @@ -182,12 +151,17 @@ private: QFontEngine::FaceId faceId() const override; QFontEngine::Properties properties() const override; QFixed emSquareSize() const override; - bool supportsSubPixelPositions() const override + bool supportsHorizontalSubPixelPositions() const override { return default_hint_style == HintLight || default_hint_style == HintNone; } + bool supportsVerticalSubPixelPositions() const override + { + return supportsHorizontalSubPixelPositions(); + } + bool getSfntTableData(uint tag, uchar *buffer, uint *length) const override; int synthesized() const override; @@ -212,24 +186,26 @@ private: void addOutlineToPath(qreal x, qreal y, const QGlyphLayout &glyphs, QPainterPath *path, QTextItem::RenderFlags flags) override; - bool stringToCMap(const QChar *str, int len, QGlyphLayout *glyphs, int *nglyphs, ShaperFlags flags) const override; + int stringToCMap(const QChar *str, int len, QGlyphLayout *glyphs, int *nglyphs, ShaperFlags flags) const override; glyph_metrics_t boundingBox(const QGlyphLayout &glyphs) override; glyph_metrics_t boundingBox(glyph_t glyph) override; glyph_metrics_t boundingBox(glyph_t glyph, const QTransform &matrix) override; void recalcAdvances(QGlyphLayout *glyphs, ShaperFlags flags) const override; - QImage alphaMapForGlyph(glyph_t g) override { return alphaMapForGlyph(g, 0); } - QImage alphaMapForGlyph(glyph_t, QFixed) override; - QImage alphaMapForGlyph(glyph_t glyph, QFixed subPixelPosition, const QTransform &t) override; - QImage alphaRGBMapForGlyph(glyph_t, QFixed subPixelPosition, const QTransform &t) override; - QImage bitmapForGlyph(glyph_t, QFixed subPixelPosition, const QTransform &t, const QColor &color) override; + QImage alphaMapForGlyph(glyph_t g) override { return alphaMapForGlyph(g, QFixedPoint()); } + QImage alphaMapForGlyph(glyph_t, const QFixedPoint &) override; + QImage alphaMapForGlyph(glyph_t glyph, const QFixedPoint &subPixelPosition, const QTransform &t) override; + QImage alphaRGBMapForGlyph(glyph_t, const QFixedPoint &subPixelPosition, const QTransform &t) override; + QImage bitmapForGlyph(glyph_t, const QFixedPoint &subPixelPosition, const QTransform &t, const QColor &color) override; glyph_metrics_t alphaMapBoundingBox(glyph_t glyph, - QFixed subPixelPosition, + const QFixedPoint &subPixelPosition, const QTransform &matrix, QFontEngine::GlyphFormat format) override; - Glyph *glyphData(glyph_t glyph, QFixed subPixelPosition, - GlyphFormat neededFormat, const QTransform &t) override; + Glyph *glyphData(glyph_t glyph, + const QFixedPoint &subPixelPosition, + GlyphFormat neededFormat, + const QTransform &t) override; bool hasInternalCaching() const override { return cacheEnabled; } bool expectsGammaCorrectedBlending() const override; @@ -252,10 +228,24 @@ private: inline bool isBitmapFont() const { return defaultFormat == Format_Mono; } inline bool isScalableBitmap() const { return freetype->isScalableBitmap(); } - inline Glyph *loadGlyph(uint glyph, QFixed subPixelPosition, GlyphFormat format = Format_None, bool fetchMetricsOnly = false, bool disableOutlineDrawing = false) const + inline Glyph *loadGlyph(uint glyph, + const QFixedPoint &subPixelPosition, + GlyphFormat format = Format_None, + bool fetchMetricsOnly = false, + bool disableOutlineDrawing = false) const { return loadGlyph(cacheEnabled ? &defaultGlyphSet : nullptr, glyph, subPixelPosition, format, fetchMetricsOnly, disableOutlineDrawing); } - Glyph *loadGlyph(QGlyphSet *set, uint glyph, QFixed subPixelPosition, GlyphFormat = Format_None, bool fetchMetricsOnly = false, bool disableOutlineDrawing = false) const; - Glyph *loadGlyphFor(glyph_t g, QFixed subPixelPosition, GlyphFormat format, const QTransform &t, bool fetchBoundingBox = false, bool disableOutlineDrawing = false); + Glyph *loadGlyph(QGlyphSet *set, + uint glyph, + const QFixedPoint &subPixelPosition, + GlyphFormat = Format_None, + bool fetchMetricsOnly = false, + bool disableOutlineDrawing = false) const; + Glyph *loadGlyphFor(glyph_t g, + const QFixedPoint &subPixelPosition, + GlyphFormat format, + const QTransform &t, + bool fetchBoundingBox = false, + bool disableOutlineDrawing = false); QGlyphSet *loadGlyphSet(const QTransform &matrix); @@ -279,7 +269,7 @@ private: HintStyle defaultHintStyle() const { return default_hint_style; } static QFontEngineFT *create(const QFontDef &fontDef, FaceId faceId, const QByteArray &fontData = QByteArray()); - static QFontEngineFT *create(const QByteArray &fontData, qreal pixelSize, QFont::HintingPreference hintingPreference); + static QFontEngineFT *create(const QByteArray &fontData, qreal pixelSize, QFont::HintingPreference hintingPreference, const QMap<QFont::Tag, float> &variableAxisValue); protected: @@ -338,15 +328,17 @@ private: QFixed scalableBitmapScaleFactor; }; -Q_DECLARE_TYPEINFO(QFontEngineFT::QGlyphSet, Q_MOVABLE_TYPE); - -inline size_t qHash(const QFontEngineFT::GlyphAndSubPixelPosition &g) +inline size_t qHash(const QFontEngineFT::GlyphAndSubPixelPosition &g, size_t seed = 0) { - return (g.glyph << 8) | (g.subPixelPosition * 10).round().toInt(); + return qHashMulti(seed, + g.glyph, + g.subPixelPosition.x.value(), + g.subPixelPosition.y.value()); } -inline QFontEngineFT::Glyph *QFontEngineFT::QGlyphSet::getGlyph(glyph_t index, QFixed subPixelPosition) const +inline QFontEngineFT::Glyph *QFontEngineFT::QGlyphSet::getGlyph(glyph_t index, + const QFixedPoint &subPixelPosition) const { if (useFastGlyphData(index, subPixelPosition)) return fast_glyph_data[index]; diff --git a/src/gui/text/freetype/qfreetypefontdatabase.cpp b/src/gui/text/freetype/qfreetypefontdatabase.cpp index ac7520c495..018e590ac2 100644 --- a/src/gui/text/freetype/qfreetypefontdatabase.cpp +++ b/src/gui/text/freetype/qfreetypefontdatabase.cpp @@ -1,41 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the plugins of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2016 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 "qfreetypefontdatabase_p.h" @@ -46,6 +10,8 @@ #include <QtCore/QLibraryInfo> #include <QtCore/QDir> #include <QtCore/QtEndian> +#include <QtCore/QLoggingCategory> +#include <QtCore/QUuid> #undef QT_NO_FREETYPE #include "qfontengine_ft_p.h" @@ -54,8 +20,16 @@ #include FT_TRUETYPE_TABLES_H #include FT_ERRORS_H +#include FT_MULTIPLE_MASTERS_H +#include FT_SFNT_NAMES_H +#include FT_TRUETYPE_IDS_H + QT_BEGIN_NAMESPACE +Q_DECLARE_LOGGING_CATEGORY(lcFontDb) + +using namespace Qt::StringLiterals; + void QFreeTypeFontDatabase::populateFontDatabase() { QString fontpath = fontDir(); @@ -68,14 +42,14 @@ void QFreeTypeFontDatabase::populateFontDatabase() return; } - QStringList nameFilters; - nameFilters << QLatin1String("*.ttf") - << QLatin1String("*.ttc") - << QLatin1String("*.pfa") - << QLatin1String("*.pfb") - << QLatin1String("*.otf"); + static const QString nameFilters[] = { + u"*.ttf"_s, + u"*.pfa"_s, + u"*.pfb"_s, + u"*.otf"_s, + }; - const auto fis = dir.entryInfoList(nameFilters, QDir::Files); + const auto fis = dir.entryInfoList(QStringList::fromReadOnlyData(nameFilters), QDir::Files); for (const QFileInfo &fi : fis) { const QByteArray file = QFile::encodeName(fi.absoluteFilePath()); QFreeTypeFontDatabase::addTTFile(QByteArray(), file); @@ -88,14 +62,24 @@ QFontEngine *QFreeTypeFontDatabase::fontEngine(const QFontDef &fontDef, void *us QFontEngine::FaceId faceId; faceId.filename = QFile::encodeName(fontfile->fileName); faceId.index = fontfile->indexValue; + faceId.instanceIndex = fontfile->instanceIndex; + faceId.variableAxes = fontDef.variableAxisValues; + + // Make sure the FaceId compares uniquely in cases where a + // file name is not provided. + if (faceId.filename.isEmpty()) { + QUuid::Id128Bytes id{}; + memcpy(&id, &usrPtr, sizeof(usrPtr)); + faceId.uuid = QUuid(id).toByteArray(); + } - return QFontEngineFT::create(fontDef, faceId); + return QFontEngineFT::create(fontDef, faceId, fontfile->data); } QFontEngine *QFreeTypeFontDatabase::fontEngine(const QByteArray &fontData, qreal pixelSize, QFont::HintingPreference hintingPreference) { - return QFontEngineFT::create(fontData, pixelSize, hintingPreference); + return QFontEngineFT::create(fontData, pixelSize, hintingPreference, {}); } QStringList QFreeTypeFontDatabase::addApplicationFont(const QByteArray &fontData, const QString &fileName, QFontDatabasePrivate::ApplicationFont *applicationFont) @@ -111,6 +95,114 @@ void QFreeTypeFontDatabase::releaseHandle(void *handle) extern FT_Library qt_getFreetype(); +void QFreeTypeFontDatabase::addNamedInstancesForFace(void *face_, + int faceIndex, + const QString &family, + const QString &styleName, + QFont::Weight weight, + QFont::Stretch stretch, + QFont::Style style, + bool fixedPitch, + const QSupportedWritingSystems &writingSystems, + const QByteArray &fileName, + const QByteArray &fontData) +{ + FT_Face face = reinterpret_cast<FT_Face>(face_); + + // Note: The following does not actually depend on API from 2.9, but the + // FT_Set_Named_Instance() was added in 2.9, so to avoid populating the database with + // named instances that cannot be selected, we disable the feature on older Freetype + // versions. +#if (FREETYPE_MAJOR*10000 + FREETYPE_MINOR*100 + FREETYPE_PATCH) >= 20900 + FT_MM_Var *var = nullptr; + FT_Get_MM_Var(face, &var); + if (var != nullptr) { + std::unique_ptr<FT_MM_Var, void(*)(FT_MM_Var*)> varGuard(var, [](FT_MM_Var *res) { + FT_Done_MM_Var(qt_getFreetype(), res); + }); + + for (FT_UInt i = 0; i < var->num_namedstyles; ++i) { + FT_UInt id = var->namedstyle[i].strid; + + QFont::Weight instanceWeight = weight; + QFont::Stretch instanceStretch = stretch; + QFont::Style instanceStyle = style; + for (FT_UInt axis = 0; axis < var->num_axis; ++axis) { + if (var->axis[axis].tag == QFont::Tag("wght").value()) { + instanceWeight = QFont::Weight(var->namedstyle[i].coords[axis] >> 16); + } else if (var->axis[axis].tag == QFont::Tag("wdth").value()) { + instanceStretch = QFont::Stretch(var->namedstyle[i].coords[axis] >> 16); + } else if (var->axis[axis].tag == QFont::Tag("ital").value()) { + FT_UInt ital = var->namedstyle[i].coords[axis] >> 16; + if (ital == 1) + instanceStyle = QFont::StyleItalic; + else + instanceStyle = QFont::StyleNormal; + } + } + + FT_UInt count = FT_Get_Sfnt_Name_Count(face); + for (FT_UInt j = 0; j < count; ++j) { + FT_SfntName name; + if (FT_Get_Sfnt_Name(face, j, &name)) + continue; + + if (name.name_id != id) + continue; + + // Only support Unicode for now + if (name.encoding_id != TT_MS_ID_UNICODE_CS) + continue; + + // Sfnt names stored as UTF-16BE + QString instanceName; + for (FT_UInt k = 0; k < name.string_len; k += 2) + instanceName += QChar((name.string[k] << 8) + name.string[k + 1]); + if (instanceName != styleName) { + FontFile *variantFontFile = new FontFile{ + QFile::decodeName(fileName), + faceIndex, + int(i), + fontData + }; + + qCDebug(lcFontDb) << "Registering named instance" << i + << ":" << instanceName + << "for font family" << family + << "with weight" << instanceWeight + << ", style" << instanceStyle + << ", stretch" << instanceStretch; + + registerFont(family, + instanceName, + QString(), + instanceWeight, + instanceStyle, + instanceStretch, + true, + true, + 0, + fixedPitch, + writingSystems, + variantFontFile); + } + } + } + } +#else + Q_UNUSED(face); + Q_UNUSED(family); + Q_UNUSED(styleName); + Q_UNUSED(weight); + Q_UNUSED(stretch); + Q_UNUSED(style); + Q_UNUSED(fixedPitch); + Q_UNUSED(writingSystems); + Q_UNUSED(fontData); +#endif + +} + QStringList QFreeTypeFontDatabase::addTTFile(const QByteArray &fontData, const QByteArray &file, QFontDatabasePrivate::ApplicationFont *applicationFont) { FT_Library library = qt_getFreetype(); @@ -153,6 +245,7 @@ QStringList QFreeTypeFontDatabase::addTTFile(const QByteArray &fontData, const Q } } + QFont::Stretch stretch = QFont::Unstretched; TT_OS2 *os2 = (TT_OS2 *)FT_Get_Sfnt_Table(face, ft_sfnt_os2); if (os2) { quint32 unicodeRange[4] = { @@ -191,14 +284,46 @@ QStringList QFreeTypeFontDatabase::addTTFile(const QByteArray &fontData, const Q else if (w <= 10) weight = QFont::Black; } + + switch (os2->usWidthClass) { + case 1: + stretch = QFont::UltraCondensed; + break; + case 2: + stretch = QFont::ExtraCondensed; + break; + case 3: + stretch = QFont::Condensed; + break; + case 4: + stretch = QFont::SemiCondensed; + break; + case 5: + stretch = QFont::Unstretched; + break; + case 6: + stretch = QFont::SemiExpanded; + break; + case 7: + stretch = QFont::Expanded; + break; + case 8: + stretch = QFont::ExtraExpanded; + break; + case 9: + stretch = QFont::UltraExpanded; + break; + } } QString family = QString::fromLatin1(face->family_name); - FontFile *fontFile = new FontFile; - fontFile->fileName = QFile::decodeName(file); - fontFile->indexValue = index; + FontFile *fontFile = new FontFile{ + QFile::decodeName(file), + index, + -1, + fontData + }; - QFont::Stretch stretch = QFont::Unstretched; QString styleName = QString::fromLatin1(face->style_name); if (applicationFont != nullptr) { @@ -213,6 +338,9 @@ QStringList QFreeTypeFontDatabase::addTTFile(const QByteArray &fontData, const Q } registerFont(family, styleName, QString(), weight, style, stretch, true, true, 0, fixedPitch, writingSystems, fontFile); + + addNamedInstancesForFace(face, index, family, styleName, weight, stretch, style, fixedPitch, writingSystems, file, fontData); + families.append(family); FT_Done_Face(face); @@ -221,4 +349,13 @@ QStringList QFreeTypeFontDatabase::addTTFile(const QByteArray &fontData, const Q return families; } +bool QFreeTypeFontDatabase::supportsVariableApplicationFonts() const +{ +#if (FREETYPE_MAJOR*10000 + FREETYPE_MINOR*100 + FREETYPE_PATCH) >= 20900 + return true; +#else + return false; +#endif +} + QT_END_NAMESPACE diff --git a/src/gui/text/freetype/qfreetypefontdatabase_p.h b/src/gui/text/freetype/qfreetypefontdatabase_p.h index 2cc725bd06..5fcec585d2 100644 --- a/src/gui/text/freetype/qfreetypefontdatabase_p.h +++ b/src/gui/text/freetype/qfreetypefontdatabase_p.h @@ -1,41 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the plugins of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2020 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 QFREETYPEFONTDATABASE_H #define QFREETYPEFONTDATABASE_H @@ -54,6 +18,7 @@ #include <qpa/qplatformfontdatabase.h> #include <QtCore/QByteArray> #include <QtCore/QString> +#include <QtCore/private/qglobal_p.h> QT_BEGIN_NAMESPACE @@ -61,6 +26,12 @@ struct FontFile { QString fileName; int indexValue; + int instanceIndex = -1; + + // Note: The data may be implicitly shared throughout the + // font database and platform font database, so be careful + // to never detach when accessing this member! + const QByteArray data; }; class Q_GUI_EXPORT QFreeTypeFontDatabase : public QPlatformFontDatabase @@ -71,6 +42,14 @@ public: QFontEngine *fontEngine(const QByteArray &fontData, qreal pixelSize, QFont::HintingPreference hintingPreference) override; QStringList addApplicationFont(const QByteArray &fontData, const QString &fileName, QFontDatabasePrivate::ApplicationFont *applicationFont = nullptr) override; void releaseHandle(void *handle) override; + bool supportsVariableApplicationFonts() const override; + + static void addNamedInstancesForFace(void *face, int faceIndex, + const QString &family, const QString &styleName, + QFont::Weight weight, QFont::Stretch stretch, + QFont::Style style, bool fixedPitch, + const QSupportedWritingSystems &writingSystems, + const QByteArray &fileName, const QByteArray &fontData); static QStringList addTTFile(const QByteArray &fontData, const QByteArray &file, QFontDatabasePrivate::ApplicationFont *applicationFont = nullptr); }; |