diff options
Diffstat (limited to 'src/gui/text/windows/qwindowsfontdatabase.cpp')
-rw-r--r-- | src/gui/text/windows/qwindowsfontdatabase.cpp | 1235 |
1 files changed, 1235 insertions, 0 deletions
diff --git a/src/gui/text/windows/qwindowsfontdatabase.cpp b/src/gui/text/windows/qwindowsfontdatabase.cpp new file mode 100644 index 0000000000..7af20df94c --- /dev/null +++ b/src/gui/text/windows/qwindowsfontdatabase.cpp @@ -0,0 +1,1235 @@ +/**************************************************************************** +** +** 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$ +** +****************************************************************************/ + +#include "qwindowsfontdatabase_p.h" +#include "qwindowsfontdatabase_ft_p.h" // for default font +#include "qwindowsfontengine_p.h" +#include "qwindowsfontenginedirectwrite_p.h" +#include <QtCore/qt_windows.h> + +#include <QtGui/QFont> +#include <QtGui/QGuiApplication> +#include <QtGui/private/qhighdpiscaling_p.h> + +#include <QtCore/qmath.h> +#include <QtCore/QDebug> +#include <QtCore/QFile> +#include <QtCore/QtEndian> +#include <QtCore/private/qsystemlibrary_p.h> +#include <QtCore/private/qwinregistry_p.h> + +#include <wchar.h> + +#if !defined(QT_NO_DIRECTWRITE) +# if defined(QT_USE_DIRECTWRITE2) +# include <dwrite_2.h> +# else +# include <dwrite.h> +# endif +# include <d2d1.h> +# include "qwindowsdirectwritefontdatabase_p.h" +#endif + +QT_BEGIN_NAMESPACE + +Q_LOGGING_CATEGORY(lcQpaFonts, "qt.qpa.fonts") + +#ifndef QT_NO_DIRECTWRITE +// ### fixme: Consider direct linking of dwrite.dll once Windows Vista pre SP2 is dropped (QTBUG-49711) + +typedef HRESULT (WINAPI *DWriteCreateFactoryType)(DWRITE_FACTORY_TYPE, const IID &, IUnknown **); + +static inline bool useDirectWrite(QFont::HintingPreference hintingPreference, + const QString &familyName = QString(), + bool isColorFont = false) +{ + const unsigned options = QWindowsFontDatabase::fontOptions(); + if (Q_UNLIKELY(options & QWindowsFontDatabase::DontUseDirectWriteFonts)) + return false; + + // At some scales, GDI will misrender the MingLiU font, so we force use of + // DirectWrite to work around the issue. + if (Q_UNLIKELY(familyName.startsWith(QLatin1String("MingLiU")))) + return true; + + if (isColorFont) + return (options & QWindowsFontDatabase::DontUseColorFonts) == 0; + + return hintingPreference == QFont::PreferNoHinting + || hintingPreference == QFont::PreferVerticalHinting + || (QHighDpiScaling::isActive() && hintingPreference == QFont::PreferDefaultHinting); +} +#endif // !QT_NO_DIRECTWRITE + +/*! + \struct QWindowsFontEngineData + \brief Static constant data shared by the font engines. +*/ + +QWindowsFontEngineData::QWindowsFontEngineData() + : fontSmoothingGamma(QWindowsFontDatabase::fontSmoothingGamma()) +{ + // from qapplication_win.cpp + UINT result = 0; + if (SystemParametersInfo(SPI_GETFONTSMOOTHINGTYPE, 0, &result, 0)) + clearTypeEnabled = (result == FE_FONTSMOOTHINGCLEARTYPE); + + const qreal gray_gamma = 2.31; + for (int i=0; i<256; ++i) + pow_gamma[i] = uint(qRound(qPow(i / qreal(255.), gray_gamma) * 2047)); + + HDC displayDC = GetDC(0); + hdc = CreateCompatibleDC(displayDC); + ReleaseDC(0, displayDC); +} + +unsigned QWindowsFontDatabase::m_fontOptions = 0; + +void QWindowsFontDatabase::setFontOptions(unsigned options) +{ + m_fontOptions = options & (QWindowsFontDatabase::DontUseDirectWriteFonts | + QWindowsFontDatabase::DontUseColorFonts); +} + +unsigned QWindowsFontDatabase::fontOptions() +{ + return m_fontOptions; +} + +qreal QWindowsFontDatabase::fontSmoothingGamma() +{ + int winSmooth; + qreal result = 1; + if (SystemParametersInfo(0x200C /* SPI_GETFONTSMOOTHINGCONTRAST */, 0, &winSmooth, 0)) + result = qreal(winSmooth) / qreal(1000.0); + + // Safeguard ourselves against corrupt registry values... + if (result > 5 || result < 1) + result = qreal(1.4); + return result; +} + +/*! + \class QWindowsFontDatabase + \brief Font database for Windows + + \note The Qt 4.8 WIndows font database employed a mechanism of + delayed population of the database again passing a font name + to EnumFontFamiliesEx(), working around the fact that + EnumFontFamiliesEx() does not list all fonts by default. + This should be introduced to QPA as well? + + \internal +*/ + +#ifndef QT_NO_DEBUG_STREAM +QDebug operator<<(QDebug d, const QFontDef &def) +{ + QDebugStateSaver saver(d); + d.nospace(); + d.noquote(); + d << "QFontDef(Family=\"" << def.family << '"'; + if (!def.styleName.isEmpty()) + d << ", stylename=" << def.styleName; + d << ", pointsize=" << def.pointSize << ", pixelsize=" << def.pixelSize + << ", styleHint=" << def.styleHint << ", weight=" << def.weight + << ", stretch=" << def.stretch << ", hintingPreference=" + << def.hintingPreference << ')'; + return d; +} + +void QWindowsFontDatabase::debugFormat(QDebug &d, const LOGFONT &lf) +{ + d << "LOGFONT(\"" << QString::fromWCharArray(lf.lfFaceName) + << "\", lfWidth=" << lf.lfWidth << ", lfHeight=" << lf.lfHeight << ')'; +} + +QDebug operator<<(QDebug d, const LOGFONT &lf) +{ + QDebugStateSaver saver(d); + d.nospace(); + d.noquote(); + QWindowsFontDatabase::debugFormat(d, lf); + return d; +} +#endif // !QT_NO_DEBUG_STREAM + +static inline QFontDatabase::WritingSystem writingSystemFromCharSet(uchar charSet) +{ + switch (charSet) { + case ANSI_CHARSET: + case EASTEUROPE_CHARSET: + case BALTIC_CHARSET: + case TURKISH_CHARSET: + return QFontDatabase::Latin; + case GREEK_CHARSET: + return QFontDatabase::Greek; + case RUSSIAN_CHARSET: + return QFontDatabase::Cyrillic; + case HEBREW_CHARSET: + return QFontDatabase::Hebrew; + case ARABIC_CHARSET: + return QFontDatabase::Arabic; + case THAI_CHARSET: + return QFontDatabase::Thai; + case GB2312_CHARSET: + return QFontDatabase::SimplifiedChinese; + case CHINESEBIG5_CHARSET: + return QFontDatabase::TraditionalChinese; + case SHIFTJIS_CHARSET: + return QFontDatabase::Japanese; + case HANGUL_CHARSET: + case JOHAB_CHARSET: + return QFontDatabase::Korean; + case VIETNAMESE_CHARSET: + return QFontDatabase::Vietnamese; + case SYMBOL_CHARSET: + return QFontDatabase::Symbol; + default: + break; + } + return QFontDatabase::Any; +} + +#ifdef MAKE_TAG +#undef MAKE_TAG +#endif +// GetFontData expects the tags in little endian ;( +#define MAKE_TAG(ch1, ch2, ch3, ch4) (\ + (((quint32)(ch4)) << 24) | \ + (((quint32)(ch3)) << 16) | \ + (((quint32)(ch2)) << 8) | \ + ((quint32)(ch1)) \ + ) + +bool qt_localizedName(const QString &name) +{ + const QChar *c = name.unicode(); + for (int i = 0; i < name.length(); ++i) { + if (c[i].unicode() >= 0x100) + return true; + } + return false; +} + +namespace { + +static QString readName(bool unicode, const uchar *string, int length) +{ + QString out; + if (unicode) { + // utf16 + + length /= 2; + out.resize(length); + QChar *uc = out.data(); + for (int i = 0; i < length; ++i) + uc[i] = qt_getUShort(string + 2*i); + } else { + // Apple Roman + + out.resize(length); + QChar *uc = out.data(); + for (int i = 0; i < length; ++i) + uc[i] = QLatin1Char(char(string[i])); + } + return out; +} + +enum FieldTypeValue { + FamilyId = 1, + StyleId = 2, + PreferredFamilyId = 16, + PreferredStyleId = 17, +}; + +enum PlatformFieldValue { + PlatformId_Unicode = 0, + PlatformId_Apple = 1, + PlatformId_Microsoft = 3 +}; + +QFontNames qt_getCanonicalFontNames(const uchar *table, quint32 bytes) +{ + QFontNames out; + const int NameRecordSize = 12; + const int MS_LangIdEnglish = 0x009; + + // get the name table + quint16 count; + quint16 string_offset; + const unsigned char *names; + + if (bytes < 8) + return out; + + if (qt_getUShort(table) != 0) + return out; + + count = qt_getUShort(table + 2); + string_offset = qt_getUShort(table + 4); + names = table + 6; + + if (string_offset >= bytes || 6 + count*NameRecordSize > string_offset) + return out; + + enum PlatformIdType { + NotFound = 0, + Unicode = 1, + Apple = 2, + Microsoft = 3 + }; + + PlatformIdType idStatus[4] = { NotFound, NotFound, NotFound, NotFound }; + int ids[4] = { -1, -1, -1, -1 }; + + for (int i = 0; i < count; ++i) { + // search for the correct name entries + + quint16 platform_id = qt_getUShort(names + i*NameRecordSize); + quint16 encoding_id = qt_getUShort(names + 2 + i*NameRecordSize); + quint16 language_id = qt_getUShort(names + 4 + i*NameRecordSize); + quint16 name_id = qt_getUShort(names + 6 + i*NameRecordSize); + + PlatformIdType *idType = nullptr; + int *id = nullptr; + + switch (name_id) { + case FamilyId: + idType = &idStatus[0]; + id = &ids[0]; + break; + case StyleId: + idType = &idStatus[1]; + id = &ids[1]; + break; + case PreferredFamilyId: + idType = &idStatus[2]; + id = &ids[2]; + break; + case PreferredStyleId: + idType = &idStatus[3]; + id = &ids[3]; + break; + default: + continue; + } + + quint16 length = qt_getUShort(names + 8 + i*NameRecordSize); + quint16 offset = qt_getUShort(names + 10 + i*NameRecordSize); + if (DWORD(string_offset + offset + length) > bytes) + continue; + + if ((platform_id == PlatformId_Microsoft + && (encoding_id == 0 || encoding_id == 1)) + && ((language_id & 0x3ff) == MS_LangIdEnglish + || *idType < Microsoft)) { + *id = i; + *idType = Microsoft; + } + // not sure if encoding id 4 for Unicode is utf16 or ucs4... + else if (platform_id == PlatformId_Unicode && encoding_id < 4 && *idType < Unicode) { + *id = i; + *idType = Unicode; + } + else if (platform_id == PlatformId_Apple && encoding_id == 0 && language_id == 0 && *idType < Apple) { + *id = i; + *idType = Apple; + } + } + + QString strings[4]; + for (int i = 0; i < 4; ++i) { + if (idStatus[i] == NotFound) + continue; + int id = ids[i]; + quint16 length = qt_getUShort(names + 8 + id * NameRecordSize); + quint16 offset = qt_getUShort(names + 10 + id * NameRecordSize); + const unsigned char *string = table + string_offset + offset; + strings[i] = readName(idStatus[i] != Apple, string, length); + } + + out.name = strings[0]; + out.style = strings[1]; + out.preferredName = strings[2]; + out.preferredStyle = strings[3]; + return out; +} + +} // namespace + +QString qt_getEnglishName(const QString &familyName, bool includeStyle) +{ + QString i18n_name; + QString faceName = familyName; + faceName.truncate(LF_FACESIZE - 1); + + HDC hdc = GetDC( 0 ); + LOGFONT lf; + memset(&lf, 0, sizeof(LOGFONT)); + faceName.toWCharArray(lf.lfFaceName); + lf.lfFaceName[faceName.size()] = 0; + lf.lfCharSet = DEFAULT_CHARSET; + HFONT hfont = CreateFontIndirect(&lf); + + if (!hfont) { + ReleaseDC(0, hdc); + return QString(); + } + + HGDIOBJ oldobj = SelectObject( hdc, hfont ); + + const DWORD name_tag = MAKE_TAG( 'n', 'a', 'm', 'e' ); + + // get the name table + unsigned char *table = 0; + + DWORD bytes = GetFontData( hdc, name_tag, 0, 0, 0 ); + if ( bytes == GDI_ERROR ) { + // ### Unused variable + // int err = GetLastError(); + goto error; + } + + table = new unsigned char[bytes]; + GetFontData(hdc, name_tag, 0, table, bytes); + if ( bytes == GDI_ERROR ) + goto error; + + { + const QFontNames names = qt_getCanonicalFontNames(table, bytes); + i18n_name = names.name; + if (includeStyle) + i18n_name += QLatin1Char(' ') + names.style; + } +error: + delete [] table; + SelectObject( hdc, oldobj ); + DeleteObject( hfont ); + ReleaseDC( 0, hdc ); + + //qDebug("got i18n name of '%s' for font '%s'", i18n_name.latin1(), familyName.toLocal8Bit().data()); + return i18n_name; +} + +// Note this duplicates parts of qt_getEnglishName, we should try to unify the two functions. +QFontNames qt_getCanonicalFontNames(const LOGFONT &lf) +{ + QFontNames fontNames; + HDC hdc = GetDC(0); + HFONT hfont = CreateFontIndirect(&lf); + + if (!hfont) { + ReleaseDC(0, hdc); + return fontNames; + } + + HGDIOBJ oldobj = SelectObject(hdc, hfont); + + // get the name table + QByteArray table; + const DWORD name_tag = MAKE_TAG('n', 'a', 'm', 'e'); + DWORD bytes = GetFontData(hdc, name_tag, 0, 0, 0); + if (bytes != GDI_ERROR) { + table.resize(bytes); + + if (GetFontData(hdc, name_tag, 0, table.data(), bytes) != GDI_ERROR) + fontNames = qt_getCanonicalFontNames(reinterpret_cast<const uchar*>(table.constData()), bytes); + } + + SelectObject(hdc, oldobj); + DeleteObject(hfont); + ReleaseDC(0, hdc); + + return fontNames; +} + +static QChar *createFontFile(const QString &faceName) +{ + QChar *faceNamePtr = nullptr; + if (!faceName.isEmpty()) { + const int nameLength = qMin(faceName.length(), LF_FACESIZE - 1); + faceNamePtr = new QChar[nameLength + 1]; + memcpy(static_cast<void *>(faceNamePtr), faceName.utf16(), sizeof(wchar_t) * nameLength); + faceNamePtr[nameLength] = 0; + } + return faceNamePtr; +} + +namespace { + struct StoreFontPayload { + StoreFontPayload(const QString &family, + QWindowsFontDatabase *fontDatabase) + : populatedFontFamily(family) + , windowsFontDatabase(fontDatabase) + {} + + QString populatedFontFamily; + QSet<FontAndStyle> foundFontAndStyles; + QWindowsFontDatabase *windowsFontDatabase; + }; +} + +static bool addFontToDatabase(QString familyName, + QString styleName, + const LOGFONT &logFont, + const TEXTMETRIC *textmetric, + const FONTSIGNATURE *signature, + int type, + StoreFontPayload *sfp) +{ + // the "@family" fonts are just the same as "family". Ignore them. + if (familyName.isEmpty() || familyName.at(0) == QLatin1Char('@') || familyName.startsWith(QLatin1String("WST_"))) + return false; + + uchar charSet = logFont.lfCharSet; + + static const int SMOOTH_SCALABLE = 0xffff; + const QString foundryName; // No such concept. + const bool fixed = !(textmetric->tmPitchAndFamily & TMPF_FIXED_PITCH); + const bool ttf = (textmetric->tmPitchAndFamily & TMPF_TRUETYPE); + const bool scalable = textmetric->tmPitchAndFamily & (TMPF_VECTOR|TMPF_TRUETYPE); + const int size = scalable ? SMOOTH_SCALABLE : textmetric->tmHeight; + const QFont::Style style = textmetric->tmItalic ? QFont::StyleItalic : QFont::StyleNormal; + const bool antialias = false; + const QFont::Weight weight = QPlatformFontDatabase::weightFromInteger(textmetric->tmWeight); + const QFont::Stretch stretch = QFont::Unstretched; + +#ifndef QT_NO_DEBUG_OUTPUT + if (lcQpaFonts().isDebugEnabled()) { + QString message; + QTextStream str(&message); + str << __FUNCTION__ << ' ' << familyName << ' ' << charSet << " TTF=" << ttf; + if (type & DEVICE_FONTTYPE) + str << " DEVICE"; + if (type & RASTER_FONTTYPE) + str << " RASTER"; + if (type & TRUETYPE_FONTTYPE) + str << " TRUETYPE"; + str << " scalable=" << scalable << " Size=" << size + << " Style=" << style << " Weight=" << weight + << " stretch=" << stretch; + qCDebug(lcQpaFonts) << message; + } +#endif + QString englishName; + QString faceName; + + QString subFamilyName; + QString subFamilyStyle; + // Look-up names registered in the font + QFontNames canonicalNames = qt_getCanonicalFontNames(logFont); + if (qt_localizedName(familyName) && !canonicalNames.name.isEmpty()) + englishName = canonicalNames.name; + if (!canonicalNames.preferredName.isEmpty()) { + subFamilyName = familyName; + subFamilyStyle = styleName; + faceName = familyName; // Remember the original name for later lookups + familyName = canonicalNames.preferredName; + styleName = canonicalNames.preferredStyle; + } + + QSupportedWritingSystems writingSystems; + if (type & TRUETYPE_FONTTYPE) { + Q_ASSERT(signature); + quint32 unicodeRange[4] = { + signature->fsUsb[0], signature->fsUsb[1], + signature->fsUsb[2], signature->fsUsb[3] + }; + quint32 codePageRange[2] = { + signature->fsCsb[0], signature->fsCsb[1] + }; + writingSystems = QPlatformFontDatabase::writingSystemsFromTrueTypeBits(unicodeRange, codePageRange); + // ### Hack to work around problem with Thai text on Windows 7. Segoe UI contains + // the symbol for Baht, and Windows thus reports that it supports the Thai script. + // Since it's the default UI font on this platform, most widgets will be unable to + // display Thai text by default. As a temporary work around, we special case Segoe UI + // and remove the Thai script from its list of supported writing systems. + if (writingSystems.supported(QFontDatabase::Thai) && + familyName == QLatin1String("Segoe UI")) + writingSystems.setSupported(QFontDatabase::Thai, false); + } else { + const QFontDatabase::WritingSystem ws = writingSystemFromCharSet(charSet); + if (ws != QFontDatabase::Any) + writingSystems.setSupported(ws); + } + + // We came here from populating a different font family, so we have + // to ensure the entire typographic family is populated before we + // mark it as such inside registerFont() + if (!subFamilyName.isEmpty() + && familyName != subFamilyName + && sfp->populatedFontFamily != familyName + && !QPlatformFontDatabase::isFamilyPopulated(familyName)) { + sfp->windowsFontDatabase->populateFamily(familyName); + } + + QPlatformFontDatabase::registerFont(familyName, styleName, foundryName, weight, + style, stretch, antialias, scalable, size, fixed, writingSystems, createFontFile(faceName)); + + // add fonts windows can generate for us: + if (weight <= QFont::DemiBold && styleName.isEmpty()) + QPlatformFontDatabase::registerFont(familyName, QString(), foundryName, QFont::Bold, + style, stretch, antialias, scalable, size, fixed, writingSystems, createFontFile(faceName)); + if (style != QFont::StyleItalic && styleName.isEmpty()) + QPlatformFontDatabase::registerFont(familyName, QString(), foundryName, weight, + QFont::StyleItalic, stretch, antialias, scalable, size, fixed, writingSystems, createFontFile(faceName)); + if (weight <= QFont::DemiBold && style != QFont::StyleItalic && styleName.isEmpty()) + QPlatformFontDatabase::registerFont(familyName, QString(), foundryName, QFont::Bold, + QFont::StyleItalic, stretch, antialias, scalable, size, fixed, writingSystems, createFontFile(faceName)); + + if (!subFamilyName.isEmpty() && familyName != subFamilyName) { + QPlatformFontDatabase::registerFont(subFamilyName, subFamilyStyle, foundryName, weight, + style, stretch, antialias, scalable, size, fixed, writingSystems, createFontFile(faceName)); + } + + if (!englishName.isEmpty() && englishName != familyName) + QPlatformFontDatabase::registerAliasToFontFamily(familyName, englishName); + + return true; +} + +static int QT_WIN_CALLBACK storeFont(const LOGFONT *logFont, const TEXTMETRIC *textmetric, + DWORD type, LPARAM lparam) +{ + const ENUMLOGFONTEX *f = reinterpret_cast<const ENUMLOGFONTEX *>(logFont); + const QString familyName = QString::fromWCharArray(f->elfLogFont.lfFaceName); + const QString styleName = QString::fromWCharArray(f->elfStyle); + + // NEWTEXTMETRICEX (passed for TT fonts) is a NEWTEXTMETRIC, which according + // to the documentation is identical to a TEXTMETRIC except for the last four + // members, which we don't use anyway + const FONTSIGNATURE *signature = nullptr; + StoreFontPayload *sfp = reinterpret_cast<StoreFontPayload *>(lparam); + Q_ASSERT(sfp != nullptr); + if (type & TRUETYPE_FONTTYPE) { + signature = &reinterpret_cast<const NEWTEXTMETRICEX *>(textmetric)->ntmFontSig; + // We get a callback for each script-type supported, but we register them all + // at once using the signature, so we only need one call to addFontToDatabase(). + FontAndStyle fontAndStyle = {familyName, styleName}; + if (sfp->foundFontAndStyles.contains(fontAndStyle)) + return 1; + sfp->foundFontAndStyles.insert(fontAndStyle); + } + addFontToDatabase(familyName, styleName, *logFont, textmetric, signature, type, sfp); + + // keep on enumerating + return 1; +} + +void QWindowsFontDatabase::populateFamily(const QString &familyName) +{ + qCDebug(lcQpaFonts) << familyName; + if (familyName.size() >= LF_FACESIZE) { + qCWarning(lcQpaFonts) << "Unable to enumerate family '" << familyName << '\''; + return; + } + HDC dummy = GetDC(0); + LOGFONT lf; + lf.lfCharSet = DEFAULT_CHARSET; + familyName.toWCharArray(lf.lfFaceName); + lf.lfFaceName[familyName.size()] = 0; + lf.lfPitchAndFamily = 0; + StoreFontPayload sfp(familyName, this); + EnumFontFamiliesEx(dummy, &lf, storeFont, reinterpret_cast<intptr_t>(&sfp), 0); + ReleaseDC(0, dummy); +} + +static int QT_WIN_CALLBACK populateFontFamilies(const LOGFONT *logFont, const TEXTMETRIC *textmetric, + DWORD, LPARAM) +{ + // the "@family" fonts are just the same as "family". Ignore them. + const ENUMLOGFONTEX *f = reinterpret_cast<const ENUMLOGFONTEX *>(logFont); + const wchar_t *faceNameW = f->elfLogFont.lfFaceName; + if (faceNameW[0] && faceNameW[0] != L'@' && wcsncmp(faceNameW, L"WST_", 4)) { + const QString faceName = QString::fromWCharArray(faceNameW); + QPlatformFontDatabase::registerFontFamily(faceName); + // Register current font's english name as alias + const bool ttf = (textmetric->tmPitchAndFamily & TMPF_TRUETYPE); + if (ttf && qt_localizedName(faceName)) { + const QString englishName = qt_getEnglishName(faceName); + if (!englishName.isEmpty()) + QPlatformFontDatabase::registerAliasToFontFamily(faceName, englishName); + } + } + return 1; // continue +} + +void QWindowsFontDatabase::addDefaultEUDCFont() +{ + const QString path = QWinRegistryKey(HKEY_CURRENT_USER, LR"(EUDC\1252)") + .stringValue(L"SystemDefaultEUDCFont"); + if (!path.isEmpty()) { + QFile file(path); + if (!file.open(QIODevice::ReadOnly)) { + qCWarning(lcQpaFonts) << "Unable to open default EUDC font:" << path; + return; + } + + m_eudcFonts = addApplicationFont(file.readAll(), path); + } +} + +void QWindowsFontDatabase::populateFontDatabase() +{ + removeApplicationFonts(); + HDC dummy = GetDC(0); + LOGFONT lf; + lf.lfCharSet = DEFAULT_CHARSET; + lf.lfFaceName[0] = 0; + lf.lfPitchAndFamily = 0; + EnumFontFamiliesEx(dummy, &lf, populateFontFamilies, 0, 0); + ReleaseDC(0, dummy); + // Work around EnumFontFamiliesEx() not listing the system font. + QString systemDefaultFamily = QWindowsFontDatabase::systemDefaultFont().family(); + if (QPlatformFontDatabase::resolveFontFamilyAlias(systemDefaultFamily) == systemDefaultFamily) + QPlatformFontDatabase::registerFontFamily(systemDefaultFamily); + addDefaultEUDCFont(); +} + +QWindowsFontDatabase::QWindowsFontDatabase() +{ + // Properties accessed by QWin32PrintEngine (Qt Print Support) + static const int hfontMetaTypeId = qRegisterMetaType<HFONT>(); + static const int logFontMetaTypeId = qRegisterMetaType<LOGFONT>(); + Q_UNUSED(hfontMetaTypeId) + Q_UNUSED(logFontMetaTypeId) + + if (lcQpaFonts().isDebugEnabled()) { + QSharedPointer<QWindowsFontEngineData> d = data(); + qCDebug(lcQpaFonts) << __FUNCTION__ << "Clear type: " + << d->clearTypeEnabled << "gamma: " << d->fontSmoothingGamma; + } +} + +QWindowsFontDatabase::~QWindowsFontDatabase() +{ + removeApplicationFonts(); +} + +QFontEngine * QWindowsFontDatabase::fontEngine(const QFontDef &fontDef, void *handle) +{ + const QString faceName(static_cast<const QChar*>(handle)); + QFontEngine *fe = QWindowsFontDatabase::createEngine(fontDef, faceName, + defaultVerticalDPI(), + data()); + qCDebug(lcQpaFonts) << __FUNCTION__ << "FONTDEF" << fontDef << fe << handle; + return fe; +} + +QFontEngine *QWindowsFontDatabase::fontEngine(const QByteArray &fontData, qreal pixelSize, QFont::HintingPreference hintingPreference) +{ + EmbeddedFont font(fontData); + QFontEngine *fontEngine = 0; + +#if !defined(QT_NO_DIRECTWRITE) + if (!useDirectWrite(hintingPreference)) +#endif + { + GUID guid; + CoCreateGuid(&guid); + +QT_WARNING_PUSH +QT_WARNING_DISABLE_GCC("-Wstrict-aliasing") + QString uniqueFamilyName = QLatin1Char('f') + + QString::number(guid.Data1, 36) + QLatin1Char('-') + + QString::number(guid.Data2, 36) + QLatin1Char('-') + + QString::number(guid.Data3, 36) + QLatin1Char('-') + + QString::number(*reinterpret_cast<quint64 *>(guid.Data4), 36); +QT_WARNING_POP + + QString actualFontName = font.changeFamilyName(uniqueFamilyName); + if (actualFontName.isEmpty()) { + qWarning("%s: Can't change family name of font", __FUNCTION__); + return 0; + } + + DWORD count = 0; + QByteArray newFontData = font.data(); + HANDLE fontHandle = + AddFontMemResourceEx(const_cast<char *>(newFontData.constData()), + DWORD(newFontData.size()), 0, &count); + if (count == 0 && fontHandle != 0) { + RemoveFontMemResourceEx(fontHandle); + fontHandle = 0; + } + + if (fontHandle == 0) { + qWarning("%s: AddFontMemResourceEx failed", __FUNCTION__); + } else { + QFontDef request; + request.family = uniqueFamilyName; + request.pixelSize = pixelSize; + request.styleStrategy = QFont::PreferMatch; + request.hintingPreference = hintingPreference; + request.stretch = QFont::Unstretched; + + fontEngine = QWindowsFontDatabase::createEngine(request, QString(), + defaultVerticalDPI(), + data()); + + if (fontEngine) { + if (request.family != fontEngine->fontDef.family) { + qWarning("%s: Failed to load font. Got fallback instead: %s", + __FUNCTION__, qPrintable(fontEngine->fontDef.family)); + if (fontEngine->ref.loadRelaxed() == 0) + delete fontEngine; + fontEngine = 0; + } else { + Q_ASSERT(fontEngine->ref.loadRelaxed() == 0); + + // Override the generated font name + switch (fontEngine->type()) { + case QFontEngine::Win: + static_cast<QWindowsFontEngine *>(fontEngine)->setUniqueFamilyName(uniqueFamilyName); + fontEngine->fontDef.family = actualFontName; + break; +#if QT_CONFIG(directwrite) && QT_CONFIG(direct2d) + case QFontEngine::DirectWrite: + static_cast<QWindowsFontEngineDirectWrite *>(fontEngine)->setUniqueFamilyName(uniqueFamilyName); + fontEngine->fontDef.family = actualFontName; + break; +#endif // directwrite && direct2d + + default: + Q_ASSERT_X(false, Q_FUNC_INFO, "Unhandled font engine."); + } + + UniqueFontData uniqueData; + uniqueData.handle = fontHandle; + uniqueData.refCount.ref(); + m_uniqueFontData[uniqueFamilyName] = uniqueData; + } + } else { + RemoveFontMemResourceEx(fontHandle); + } + } + + // Get style and weight info + if (fontEngine != nullptr) + font.updateFromOS2Table(fontEngine); + } +#if QT_CONFIG(directwrite) && QT_CONFIG(direct2d) + else { + fontEngine = QWindowsFontDatabaseBase::fontEngine(fontData, pixelSize, hintingPreference); + } +#endif // directwrite && direct2d + + qCDebug(lcQpaFonts) << __FUNCTION__ << "FONTDATA" << fontData << pixelSize << hintingPreference << fontEngine; + return fontEngine; +} + +static QList<quint32> getTrueTypeFontOffsets(const uchar *fontData) +{ + QList<quint32> offsets; + const quint32 headerTag = *reinterpret_cast<const quint32 *>(fontData); + if (headerTag != MAKE_TAG('t', 't', 'c', 'f')) { + if (headerTag != MAKE_TAG(0, 1, 0, 0) + && headerTag != MAKE_TAG('O', 'T', 'T', 'O') + && headerTag != MAKE_TAG('t', 'r', 'u', 'e') + && headerTag != MAKE_TAG('t', 'y', 'p', '1')) + return offsets; + offsets << 0; + return offsets; + } + const quint32 numFonts = qFromBigEndian<quint32>(fontData + 8); + for (uint i = 0; i < numFonts; ++i) { + offsets << qFromBigEndian<quint32>(fontData + 12 + i * 4); + } + return offsets; +} + +static void getFontTable(const uchar *fileBegin, const uchar *data, quint32 tag, const uchar **table, quint32 *length) +{ + const quint16 numTables = qFromBigEndian<quint16>(data + 4); + for (uint i = 0; i < numTables; ++i) { + const quint32 offset = 12 + 16 * i; + if (*reinterpret_cast<const quint32 *>(data + offset) == tag) { + *table = fileBegin + qFromBigEndian<quint32>(data + offset + 8); + *length = qFromBigEndian<quint32>(data + offset + 12); + return; + } + } + *table = 0; + *length = 0; + return; +} + +static void getFamiliesAndSignatures(const QByteArray &fontData, + QList<QFontNames> *families, + QVector<FONTSIGNATURE> *signatures, + QVector<QFontValues> *values) +{ + const uchar *data = reinterpret_cast<const uchar *>(fontData.constData()); + + QList<quint32> offsets = getTrueTypeFontOffsets(data); + if (offsets.isEmpty()) + return; + + for (int i = 0; i < offsets.count(); ++i) { + const uchar *font = data + offsets.at(i); + const uchar *table; + quint32 length; + getFontTable(data, font, MAKE_TAG('n', 'a', 'm', 'e'), &table, &length); + if (!table) + continue; + QFontNames names = qt_getCanonicalFontNames(table, length); + if (names.name.isEmpty()) + continue; + + families->append(std::move(names)); + + if (values || signatures) + getFontTable(data, font, MAKE_TAG('O', 'S', '/', '2'), &table, &length); + + if (values) { + QFontValues fontValues; + if (table && length >= 64) { + // Read in some details about the font, offset calculated based on the specification + fontValues.weight = qFromBigEndian<quint16>(table + 4); + + quint16 fsSelection = qFromBigEndian<quint16>(table + 62); + fontValues.isItalic = (fsSelection & 1) != 0; + fontValues.isUnderlined = (fsSelection & (1 << 1)) != 0; + fontValues.isOverstruck = (fsSelection & (1 << 4)) != 0; + } + values->append(std::move(fontValues)); + } + + if (signatures) { + FONTSIGNATURE signature; + if (table && length >= 86) { + // Offsets taken from OS/2 table in the TrueType spec + signature.fsUsb[0] = qFromBigEndian<quint32>(table + 42); + signature.fsUsb[1] = qFromBigEndian<quint32>(table + 46); + signature.fsUsb[2] = qFromBigEndian<quint32>(table + 50); + signature.fsUsb[3] = qFromBigEndian<quint32>(table + 54); + + signature.fsCsb[0] = qFromBigEndian<quint32>(table + 78); + signature.fsCsb[1] = qFromBigEndian<quint32>(table + 82); + } else { + memset(&signature, 0, sizeof(signature)); + } + signatures->append(signature); + } + } +} + +QStringList QWindowsFontDatabase::addApplicationFont(const QByteArray &fontData, const QString &fileName, QFontDatabasePrivate::ApplicationFont *applicationFont) +{ + WinApplicationFont font; + font.fileName = fileName; + QVector<FONTSIGNATURE> signatures; + QVector<QFontValues> fontValues; + QList<QFontNames> families; + QStringList familyNames; + + if (!fontData.isEmpty()) { + getFamiliesAndSignatures(fontData, &families, &signatures, &fontValues); + if (families.isEmpty()) + return familyNames; + + DWORD dummy = 0; + font.handle = + AddFontMemResourceEx(const_cast<char *>(fontData.constData()), + DWORD(fontData.size()), 0, &dummy); + if (font.handle == 0) + return QStringList(); + + // Memory fonts won't show up in enumeration, so do add them the hard way. + for (int j = 0; j < families.count(); ++j) { + const auto &family = families.at(j); + const QString &familyName = family.name; + const QString &styleName = family.style; + familyNames << familyName; + HDC hdc = GetDC(0); + LOGFONT lf; + memset(&lf, 0, sizeof(LOGFONT)); + memcpy(lf.lfFaceName, familyName.utf16(), sizeof(wchar_t) * qMin(LF_FACESIZE - 1, familyName.size())); + lf.lfCharSet = DEFAULT_CHARSET; + const QFontValues &values = fontValues.at(j); + lf.lfWeight = values.weight; + if (values.isItalic) + lf.lfItalic = TRUE; + if (values.isOverstruck) + lf.lfStrikeOut = TRUE; + if (values.isUnderlined) + lf.lfUnderline = TRUE; + HFONT hfont = CreateFontIndirect(&lf); + HGDIOBJ oldobj = SelectObject(hdc, hfont); + + if (applicationFont != nullptr) { + QFontDatabasePrivate::ApplicationFont::Properties properties; + properties.style = values.isItalic ? QFont::StyleItalic : QFont::StyleNormal; + properties.weight = QPlatformFontDatabase::weightFromInteger(values.weight); + properties.familyName = familyName; + properties.styleName = styleName; + + applicationFont->properties.append(properties); + } + + TEXTMETRIC textMetrics; + GetTextMetrics(hdc, &textMetrics); + + StoreFontPayload sfp(familyName, this); + addFontToDatabase(familyName, styleName, lf, &textMetrics, &signatures.at(j), + TRUETYPE_FONTTYPE, &sfp); + + SelectObject(hdc, oldobj); + DeleteObject(hfont); + ReleaseDC(0, hdc); + } + } else { + QFile f(fileName); + if (!f.open(QIODevice::ReadOnly)) + return QStringList(); + QByteArray data = f.readAll(); + f.close(); + + + getFamiliesAndSignatures(data, &families, nullptr, applicationFont != nullptr ? &fontValues : nullptr); + + if (families.isEmpty()) + return QStringList(); + + if (AddFontResourceExW((wchar_t*)fileName.utf16(), FR_PRIVATE, 0) == 0) + return QStringList(); + + font.handle = 0; + + // Fonts based on files are added via populate, as they will show up in font enumeration. + for (int j = 0; j < families.count(); ++j) { + const QString familyName = families.at(j).name; + familyNames << familyName; + + if (applicationFont != nullptr) { + const QString &styleName = families.at(j).style; + const QFontValues &values = fontValues.at(j); + + QFontDatabasePrivate::ApplicationFont::Properties properties; + properties.style = values.isItalic ? QFont::StyleItalic : QFont::StyleNormal; + properties.weight = QPlatformFontDatabase::weightFromInteger(values.weight); + properties.familyName = familyName; + properties.styleName = styleName; + + applicationFont->properties.append(properties); + } + + populateFamily(familyName); + } + } + + m_applicationFonts << font; + + return familyNames; +} + +void QWindowsFontDatabase::removeApplicationFonts() +{ + for (const WinApplicationFont &font : qAsConst(m_applicationFonts)) { + if (font.handle) { + RemoveFontMemResourceEx(font.handle); + } else { + RemoveFontResourceExW(reinterpret_cast<LPCWSTR>(font.fileName.utf16()), + FR_PRIVATE, nullptr); + } + } + m_applicationFonts.clear(); + m_eudcFonts.clear(); +} + +void QWindowsFontDatabase::releaseHandle(void *handle) +{ + const QChar *faceName = reinterpret_cast<const QChar *>(handle); + delete[] faceName; +} + +QString QWindowsFontDatabase::fontDir() const +{ + const QString result = QPlatformFontDatabase::fontDir(); + qCDebug(lcQpaFonts) << __FUNCTION__ << result; + return result; +} + +bool QWindowsFontDatabase::fontsAlwaysScalable() const +{ + return false; +} + +void QWindowsFontDatabase::derefUniqueFont(const QString &uniqueFont) +{ + if (m_uniqueFontData.contains(uniqueFont)) { + if (!m_uniqueFontData[uniqueFont].refCount.deref()) { + RemoveFontMemResourceEx(m_uniqueFontData[uniqueFont].handle); + m_uniqueFontData.remove(uniqueFont); + } + } +} + +void QWindowsFontDatabase::refUniqueFont(const QString &uniqueFont) +{ + if (m_uniqueFontData.contains(uniqueFont)) + m_uniqueFontData[uniqueFont].refCount.ref(); +} + +QStringList QWindowsFontDatabase::fallbacksForFamily(const QString &family, QFont::Style style, QFont::StyleHint styleHint, QChar::Script script) const +{ + QStringList result; + result.append(familyForStyleHint(styleHint)); + result.append(m_eudcFonts); + result.append(extraTryFontsForFamily(family)); + result.append(QPlatformFontDatabase::fallbacksForFamily(family, style, styleHint, script)); + + qCDebug(lcQpaFonts) << __FUNCTION__ << family << style << styleHint + << script << result; + return result; +} + + +QFontEngine *QWindowsFontDatabase::createEngine(const QFontDef &request, const QString &faceName, + int dpi, + const QSharedPointer<QWindowsFontEngineData> &data) +{ + QFontEngine *fe = 0; + + LOGFONT lf = fontDefToLOGFONT(request, faceName); + const bool preferClearTypeAA = lf.lfQuality == CLEARTYPE_QUALITY; + + if (request.stretch != 100) { + HFONT hfont = CreateFontIndirect(&lf); + if (!hfont) { + qErrnoWarning("%s: CreateFontIndirect failed", __FUNCTION__); + hfont = QWindowsFontDatabase::systemFont(); + } + + HGDIOBJ oldObj = SelectObject(data->hdc, hfont); + TEXTMETRIC tm; + if (!GetTextMetrics(data->hdc, &tm)) + qErrnoWarning("%s: GetTextMetrics failed", __FUNCTION__); + else + lf.lfWidth = tm.tmAveCharWidth * request.stretch / 100; + SelectObject(data->hdc, oldObj); + + DeleteObject(hfont); + } + +#if QT_CONFIG(directwrite) && QT_CONFIG(direct2d) + if (data->directWriteFactory != nullptr) { + const QString fam = QString::fromWCharArray(lf.lfFaceName); + const QString nameSubstitute = QWindowsFontEngineDirectWrite::fontNameSubstitute(fam); + if (nameSubstitute != fam) { + const int nameSubstituteLength = qMin(nameSubstitute.length(), LF_FACESIZE - 1); + memcpy(lf.lfFaceName, nameSubstitute.utf16(), nameSubstituteLength * sizeof(wchar_t)); + lf.lfFaceName[nameSubstituteLength] = 0; + } + + HFONT hfont = CreateFontIndirect(&lf); + if (!hfont) { + qErrnoWarning("%s: CreateFontIndirect failed", __FUNCTION__); + } else { + HGDIOBJ oldFont = SelectObject(data->hdc, hfont); + + const QFont::HintingPreference hintingPreference = + static_cast<QFont::HintingPreference>(request.hintingPreference); + bool useDw = useDirectWrite(hintingPreference, fam); + + IDWriteFontFace *directWriteFontFace = NULL; + HRESULT hr = data->directWriteGdiInterop->CreateFontFaceFromHdc(data->hdc, &directWriteFontFace); + if (SUCCEEDED(hr)) { + bool isColorFont = false; +#if QT_CONFIG(direct2d) + IDWriteFontFace2 *directWriteFontFace2 = nullptr; + if (SUCCEEDED(directWriteFontFace->QueryInterface(__uuidof(IDWriteFontFace2), + reinterpret_cast<void **>(&directWriteFontFace2)))) { + if (directWriteFontFace2->IsColorFont()) + isColorFont = directWriteFontFace2->GetPaletteEntryCount() > 0; + + directWriteFontFace2->Release(); + } +#endif // direct2d + useDw = useDw || useDirectWrite(hintingPreference, fam, isColorFont); + qCDebug(lcQpaFonts) << __FUNCTION__ << request.family << request.pointSize + << "pt" << "hintingPreference=" << hintingPreference << "color=" << isColorFont + << dpi << "dpi" << "useDirectWrite=" << useDw; + if (useDw) { + QWindowsFontEngineDirectWrite *fedw = new QWindowsFontEngineDirectWrite(directWriteFontFace, + request.pixelSize, + data); + + wchar_t n[64]; + GetTextFace(data->hdc, 64, n); + + QFontDef fontDef = request; + fontDef.family = QString::fromWCharArray(n); + + if (isColorFont) + fedw->glyphFormat = QFontEngine::Format_ARGB; + fedw->initFontInfo(fontDef, dpi); + fe = fedw; + } + directWriteFontFace->Release(); + } else if (useDw) { + const QString errorString = qt_error_string(int(hr)); + qWarning().noquote().nospace() << "DirectWrite: CreateFontFaceFromHDC() failed (" + << errorString << ") for " << request << ' ' << lf << " dpi=" << dpi; + } + + SelectObject(data->hdc, oldFont); + DeleteObject(hfont); + } + } +#endif // directwrite direct2d + + if (!fe) { + QWindowsFontEngine *few = new QWindowsFontEngine(request.family, lf, data); + if (preferClearTypeAA) + few->glyphFormat = QFontEngine::Format_A32; + few->initFontInfo(request, dpi); + fe = few; + } + + return fe; +} + +bool QWindowsFontDatabase::isPrivateFontFamily(const QString &family) const +{ + return m_eudcFonts.contains(family) || QPlatformFontDatabase::isPrivateFontFamily(family); +} + +QT_END_NAMESPACE |