1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of the plugins of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see https://www.qt.io/terms-conditions. For further
15 ** information use the contact form at https://www.qt.io/contact-us.
16 **
17 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 3 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.LGPL3 included in the
21 ** packaging of this file. Please review the following information to
22 ** ensure the GNU Lesser General Public License version 3 requirements
23 ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24 **
25 ** GNU General Public License Usage
26 ** Alternatively, this file may be used under the terms of the GNU
27 ** General Public License version 2.0 or (at your option) the GNU General
28 ** Public license version 3 or any later version approved by the KDE Free
29 ** Qt Foundation. The licenses are as published by the Free Software
30 ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31 ** included in the packaging of this file. Please review the following
32 ** information to ensure the GNU General Public License requirements will
33 ** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34 ** https://www.gnu.org/licenses/gpl-3.0.html.
35 **
36 ** $QT_END_LICENSE$
37 **
38 ****************************************************************************/
39 
40 #include "qwindowsfontdatabase_ft_p.h"
41 #include "qwindowsfontdatabase_p.h"
42 
43 #include <QtFontDatabaseSupport/private/qfontengine_ft_p.h>
44 
45 #include <ft2build.h>
46 #include FT_TRUETYPE_TABLES_H
47 
48 #include <QtCore/QDir>
49 #include <QtCore/QDirIterator>
50 #include <QtCore/QSettings>
51 #if QT_CONFIG(regularexpression)
52 #include <QtCore/QRegularExpression>
53 #else
54 #include <QtCore/QRegExp>
55 #endif
56 #include <QtGui/QGuiApplication>
57 #include <QtGui/QFontDatabase>
58 
59 #include <wchar.h>
60 
61 QT_BEGIN_NAMESPACE
62 
writingSystemFromCharSet(uchar charSet)63 static inline QFontDatabase::WritingSystem writingSystemFromCharSet(uchar charSet)
64 {
65     switch (charSet) {
66     case ANSI_CHARSET:
67     case EASTEUROPE_CHARSET:
68     case BALTIC_CHARSET:
69     case TURKISH_CHARSET:
70         return QFontDatabase::Latin;
71     case GREEK_CHARSET:
72         return QFontDatabase::Greek;
73     case RUSSIAN_CHARSET:
74         return QFontDatabase::Cyrillic;
75     case HEBREW_CHARSET:
76         return QFontDatabase::Hebrew;
77     case ARABIC_CHARSET:
78         return QFontDatabase::Arabic;
79     case THAI_CHARSET:
80         return QFontDatabase::Thai;
81     case GB2312_CHARSET:
82         return QFontDatabase::SimplifiedChinese;
83     case CHINESEBIG5_CHARSET:
84         return QFontDatabase::TraditionalChinese;
85     case SHIFTJIS_CHARSET:
86         return QFontDatabase::Japanese;
87     case HANGUL_CHARSET:
88     case JOHAB_CHARSET:
89         return QFontDatabase::Korean;
90     case VIETNAMESE_CHARSET:
91         return QFontDatabase::Vietnamese;
92     case SYMBOL_CHARSET:
93         return QFontDatabase::Symbol;
94     default:
95         break;
96     }
97     return QFontDatabase::Any;
98 }
99 
createFontFile(const QString & fileName,int index)100 static FontFile * createFontFile(const QString &fileName, int index)
101 {
102     FontFile *fontFile = new FontFile;
103     fontFile->fileName = fileName;
104     fontFile->indexValue = index;
105     return fontFile;
106 }
107 
108 namespace {
109 struct FontKey
110 {
111     QString fileName;
112     QStringList fontNames;
113 };
114 } // namespace
115 
116 typedef QVector<FontKey> FontKeys;
117 
fontKeys()118 static FontKeys &fontKeys()
119 {
120     static FontKeys result;
121     if (result.isEmpty()) {
122         const QStringList keys = { QStringLiteral("HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Fonts"),
123                                    QStringLiteral("HKEY_CURRENT_USER\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Fonts") };
124         for (const auto key : keys) {
125             const QSettings fontRegistry(key, QSettings::NativeFormat);
126             const QStringList allKeys = fontRegistry.allKeys();
127             const QString trueType = QStringLiteral("(TrueType)");
128 #if QT_CONFIG(regularexpression)
129             const QRegularExpression sizeListMatch(QStringLiteral("\\s(\\d+,)+\\d+"));
130 #else
131             const QRegExp sizeListMatch(QLatin1String("\\s(\\d+,)+\\d+"));
132 #endif
133             Q_ASSERT(sizeListMatch.isValid());
134             const int size = allKeys.size();
135             result.reserve(result.size() + size);
136             for (int i = 0; i < size; ++i) {
137                 FontKey fontKey;
138                 const QString &registryFontKey = allKeys.at(i);
139                 fontKey.fileName = fontRegistry.value(registryFontKey).toString();
140                 QString realKey = registryFontKey;
141                 realKey.remove(trueType);
142                 realKey.remove(sizeListMatch);
143                 const auto fontNames = QStringRef(&realKey).trimmed().split(QLatin1Char('&'));
144                 fontKey.fontNames.reserve(fontNames.size());
145                 for (const QStringRef &fontName : fontNames)
146                     fontKey.fontNames.append(fontName.trimmed().toString());
147                 result.append(fontKey);
148             }
149         }
150     }
151     return result;
152 }
153 
findFontKey(const QString & name,int * indexIn=nullptr)154 static const FontKey *findFontKey(const QString &name, int *indexIn = nullptr)
155 {
156      const FontKeys &keys = fontKeys();
157      for (auto it = keys.constBegin(), cend = keys.constEnd(); it != cend; ++it) {
158          const int index = it->fontNames.indexOf(name);
159          if (index >= 0) {
160              if (indexIn)
161                  *indexIn = index;
162              return &(*it);
163          }
164      }
165      if (indexIn)
166          *indexIn = -1;
167      return nullptr;
168 }
169 
addFontToDatabase(QString familyName,QString styleName,const QString & fullName,const LOGFONT & logFont,const TEXTMETRIC * textmetric,const FONTSIGNATURE * signature,int type)170 static bool addFontToDatabase(QString familyName,
171                               QString styleName,
172                               const QString &fullName,
173                               const LOGFONT &logFont,
174                               const TEXTMETRIC *textmetric,
175                               const FONTSIGNATURE *signature,
176                               int type)
177 {
178     // the "@family" fonts are just the same as "family". Ignore them.
179     if (familyName.isEmpty() || familyName.at(0) == QLatin1Char('@') || familyName.startsWith(QLatin1String("WST_")))
180         return false;
181 
182     uchar charSet = logFont.lfCharSet;
183 
184     static const int SMOOTH_SCALABLE = 0xffff;
185     const QString foundryName; // No such concept.
186     const bool fixed = !(textmetric->tmPitchAndFamily & TMPF_FIXED_PITCH);
187     const bool ttf = (textmetric->tmPitchAndFamily & TMPF_TRUETYPE);
188     const bool scalable = textmetric->tmPitchAndFamily & (TMPF_VECTOR|TMPF_TRUETYPE);
189     const int size = scalable ? SMOOTH_SCALABLE : textmetric->tmHeight;
190     const QFont::Style style = textmetric->tmItalic ? QFont::StyleItalic : QFont::StyleNormal;
191     const bool antialias = false;
192     const QFont::Weight weight = QPlatformFontDatabase::weightFromInteger(textmetric->tmWeight);
193     const QFont::Stretch stretch = QFont::Unstretched;
194 
195 #ifndef QT_NO_DEBUG_STREAM
196     if (lcQpaFonts().isDebugEnabled()) {
197         QString message;
198         QTextStream str(&message);
199         str << __FUNCTION__ << ' ' << familyName << "::" << fullName << ' ' << charSet << " TTF=" << ttf;
200         if (type & DEVICE_FONTTYPE)
201             str << " DEVICE";
202         if (type & RASTER_FONTTYPE)
203             str << " RASTER";
204         if (type & TRUETYPE_FONTTYPE)
205             str << " TRUETYPE";
206         str << " scalable=" << scalable << " Size=" << size
207                 << " Style=" << style << " Weight=" << weight
208                 << " stretch=" << stretch;
209         qCDebug(lcQpaFonts) << message;
210     }
211 #endif
212 
213     QString englishName;
214     QString faceName = familyName;
215 
216     QString subFamilyName;
217     QString subFamilyStyle;
218     // Look-up names registered in the font
219     QFontNames canonicalNames = qt_getCanonicalFontNames(logFont);
220     if (qt_localizedName(familyName) && !canonicalNames.name.isEmpty())
221         englishName = canonicalNames.name;
222     if (!canonicalNames.preferredName.isEmpty()) {
223         subFamilyName = familyName;
224         subFamilyStyle = styleName;
225         familyName = canonicalNames.preferredName;
226         styleName = canonicalNames.preferredStyle;
227     }
228 
229     QSupportedWritingSystems writingSystems;
230     if (type & TRUETYPE_FONTTYPE) {
231         Q_ASSERT(signature);
232         quint32 unicodeRange[4] = {
233             signature->fsUsb[0], signature->fsUsb[1],
234             signature->fsUsb[2], signature->fsUsb[3]
235         };
236         quint32 codePageRange[2] = {
237             signature->fsCsb[0], signature->fsCsb[1]
238         };
239         writingSystems = QPlatformFontDatabase::writingSystemsFromTrueTypeBits(unicodeRange, codePageRange);
240         // ### Hack to work around problem with Thai text on Windows 7. Segoe UI contains
241         // the symbol for Baht, and Windows thus reports that it supports the Thai script.
242         // Since it's the default UI font on this platform, most widgets will be unable to
243         // display Thai text by default. As a temporary work around, we special case Segoe UI
244         // and remove the Thai script from its list of supported writing systems.
245         if (writingSystems.supported(QFontDatabase::Thai) &&
246                 faceName == QLatin1String("Segoe UI"))
247             writingSystems.setSupported(QFontDatabase::Thai, false);
248     } else {
249         const QFontDatabase::WritingSystem ws = writingSystemFromCharSet(charSet);
250         if (ws != QFontDatabase::Any)
251             writingSystems.setSupported(ws);
252     }
253 
254     int index = 0;
255     const FontKey *key = findFontKey(fullName, &index);
256     if (!key) {
257         // On non-English locales, the styles of the font may be localized in enumeration, but
258         // not in the registry.
259         QLocale systemLocale = QLocale::system();
260         if (systemLocale.language() != QLocale::C
261                 && systemLocale.language() != QLocale::English
262                 && styleName != QLatin1String("Italic")
263                 && styleName != QLatin1String("Bold")) {
264             key = findFontKey(qt_getEnglishName(fullName, true), &index);
265         }
266         if (!key)
267             key = findFontKey(faceName, &index);
268         if (!key && !englishName.isEmpty())
269             key = findFontKey(englishName, &index);
270         if (!key)
271             return false;
272     }
273     QString value = key->fileName;
274     if (value.isEmpty())
275         return false;
276 
277     if (!QDir::isAbsolutePath(value))
278         value.prepend(QFile::decodeName(qgetenv("windir") + "\\Fonts\\"));
279 
280     QPlatformFontDatabase::registerFont(familyName, styleName, foundryName, weight, style, stretch,
281         antialias, scalable, size, fixed, writingSystems, createFontFile(value, index));
282 
283     // add fonts windows can generate for us:
284     if (weight <= QFont::DemiBold && styleName.isEmpty())
285         QPlatformFontDatabase::registerFont(familyName, QString(), foundryName, QFont::Bold, style, stretch,
286                                             antialias, scalable, size, fixed, writingSystems, createFontFile(value, index));
287 
288     if (style != QFont::StyleItalic && styleName.isEmpty())
289         QPlatformFontDatabase::registerFont(familyName, QString(), foundryName, weight, QFont::StyleItalic, stretch,
290                                             antialias, scalable, size, fixed, writingSystems, createFontFile(value, index));
291 
292     if (weight <= QFont::DemiBold && style != QFont::StyleItalic && styleName.isEmpty())
293         QPlatformFontDatabase::registerFont(familyName, QString(), foundryName, QFont::Bold, QFont::StyleItalic, stretch,
294                                             antialias, scalable, size, fixed, writingSystems, createFontFile(value, index));
295 
296     if (!subFamilyName.isEmpty() && familyName != subFamilyName) {
297         QPlatformFontDatabase::registerFont(subFamilyName, subFamilyStyle, foundryName, weight,
298                                             style, stretch, antialias, scalable, size, fixed, writingSystems, createFontFile(value, index));
299     }
300 
301     if (!englishName.isEmpty() && englishName != familyName)
302         QPlatformFontDatabase::registerAliasToFontFamily(familyName, englishName);
303 
304     return true;
305 }
306 
storeFont(const LOGFONT * logFont,const TEXTMETRIC * textmetric,DWORD type,LPARAM lparam)307 static int QT_WIN_CALLBACK storeFont(const LOGFONT *logFont, const TEXTMETRIC *textmetric,
308                                      DWORD type, LPARAM lparam)
309 {
310     const ENUMLOGFONTEX *f = reinterpret_cast<const ENUMLOGFONTEX *>(logFont);
311     const QString faceName = QString::fromWCharArray(f->elfLogFont.lfFaceName);
312     const QString styleName = QString::fromWCharArray(f->elfStyle);
313     const QString fullName = QString::fromWCharArray(f->elfFullName);
314 
315     // NEWTEXTMETRICEX (passed for TT fonts) is a NEWTEXTMETRIC, which according
316     // to the documentation is identical to a TEXTMETRIC except for the last four
317     // members, which we don't use anyway
318     const FONTSIGNATURE *signature = nullptr;
319     if (type & TRUETYPE_FONTTYPE) {
320         signature = &reinterpret_cast<const NEWTEXTMETRICEX *>(textmetric)->ntmFontSig;
321         // We get a callback for each script-type supported, but we register them all
322         // at once using the signature, so we only need one call to addFontToDatabase().
323         QSet<QPair<QString,QString>> *foundFontAndStyles = reinterpret_cast<QSet<QPair<QString,QString>> *>(lparam);
324         QPair<QString,QString> fontAndStyle(faceName, styleName);
325         if (foundFontAndStyles->contains(fontAndStyle))
326             return 1;
327         foundFontAndStyles->insert(fontAndStyle);
328     }
329     addFontToDatabase(faceName, styleName, fullName, *logFont, textmetric, signature, type);
330 
331     // keep on enumerating
332     return 1;
333 }
334 
335 /*!
336     \brief Populate font database using EnumFontFamiliesEx().
337 
338     Normally, leaving the name empty should enumerate
339     all fonts, however, system fonts like "MS Shell Dlg 2"
340     are only found when specifying the name explicitly.
341 */
342 
populateFamily(const QString & familyName)343 void QWindowsFontDatabaseFT::populateFamily(const QString &familyName)
344 {
345     qCDebug(lcQpaFonts) << familyName;
346     if (familyName.size() >= LF_FACESIZE) {
347         qCWarning(lcQpaFonts) << "Unable to enumerate family '" << familyName << '\'';
348         return;
349     }
350     HDC dummy = GetDC(0);
351     LOGFONT lf;
352     memset(&lf, 0, sizeof(LOGFONT));
353     familyName.toWCharArray(lf.lfFaceName);
354     lf.lfFaceName[familyName.size()] = 0;
355     lf.lfCharSet = DEFAULT_CHARSET;
356     lf.lfPitchAndFamily = 0;
357     QSet<QPair<QString,QString>> foundFontAndStyles;
358     EnumFontFamiliesEx(dummy, &lf, storeFont, reinterpret_cast<intptr_t>(&foundFontAndStyles), 0);
359     ReleaseDC(0, dummy);
360 }
361 
362 // Delayed population of font families
363 
populateFontFamilies(const LOGFONT * logFont,const TEXTMETRIC * textmetric,DWORD,LPARAM)364 static int QT_WIN_CALLBACK populateFontFamilies(const LOGFONT *logFont, const TEXTMETRIC *textmetric,
365                                                 DWORD, LPARAM)
366 {
367     const ENUMLOGFONTEX *f = reinterpret_cast<const ENUMLOGFONTEX *>(logFont);
368     // the "@family" fonts are just the same as "family". Ignore them.
369     const wchar_t *faceNameW = f->elfLogFont.lfFaceName;
370     if (faceNameW[0] && faceNameW[0] != L'@' && wcsncmp(faceNameW, L"WST_", 4)) {
371         // Register only font families for which a font file exists for delayed population
372         const bool ttf = textmetric->tmPitchAndFamily & TMPF_TRUETYPE;
373         const QString faceName = QString::fromWCharArray(faceNameW);
374         const FontKey *key = findFontKey(faceName);
375         if (!key) {
376             key = findFontKey(QString::fromWCharArray(f->elfFullName));
377             if (!key && ttf && qt_localizedName(faceName))
378                 key = findFontKey(qt_getEnglishName(faceName));
379         }
380         if (key) {
381             QPlatformFontDatabase::registerFontFamily(faceName);
382             // Register current font's english name as alias
383             if (ttf && qt_localizedName(faceName)) {
384                 const QString englishName = qt_getEnglishName(faceName);
385                 if (!englishName.isEmpty())
386                     QPlatformFontDatabase::registerAliasToFontFamily(faceName, englishName);
387             }
388         }
389     }
390     return 1; // continue
391 }
392 
populateFontDatabase()393 void QWindowsFontDatabaseFT::populateFontDatabase()
394 {
395     HDC dummy = GetDC(0);
396     LOGFONT lf;
397     lf.lfCharSet = DEFAULT_CHARSET;
398     lf.lfFaceName[0] = 0;
399     lf.lfPitchAndFamily = 0;
400     EnumFontFamiliesEx(dummy, &lf, populateFontFamilies, 0, 0);
401     ReleaseDC(0, dummy);
402     // Work around EnumFontFamiliesEx() not listing the system font
403     QString systemDefaultFamily = QWindowsFontDatabase::systemDefaultFont().family();
404     if (QPlatformFontDatabase::resolveFontFamilyAlias(systemDefaultFamily) == systemDefaultFamily)
405         QPlatformFontDatabase::registerFontFamily(systemDefaultFamily);
406 }
407 
fontEngine(const QFontDef & fontDef,void * handle)408 QFontEngine * QWindowsFontDatabaseFT::fontEngine(const QFontDef &fontDef, void *handle)
409 {
410     QFontEngine *fe = QFreeTypeFontDatabase::fontEngine(fontDef, handle);
411     qCDebug(lcQpaFonts) << __FUNCTION__ << "FONTDEF" << fontDef.family << fe << handle;
412     return fe;
413 }
414 
fontEngine(const QByteArray & fontData,qreal pixelSize,QFont::HintingPreference hintingPreference)415 QFontEngine *QWindowsFontDatabaseFT::fontEngine(const QByteArray &fontData, qreal pixelSize, QFont::HintingPreference hintingPreference)
416 {
417     QFontEngine *fe = QFreeTypeFontDatabase::fontEngine(fontData, pixelSize, hintingPreference);
418     qCDebug(lcQpaFonts) << __FUNCTION__ << "FONTDATA" << fontData << pixelSize << hintingPreference << fe;
419     return fe;
420 }
421 
fallbacksForFamily(const QString & family,QFont::Style style,QFont::StyleHint styleHint,QChar::Script script) const422 QStringList QWindowsFontDatabaseFT::fallbacksForFamily(const QString &family, QFont::Style style, QFont::StyleHint styleHint, QChar::Script script) const
423 {
424     QStringList result;
425     result.append(QWindowsFontDatabase::familyForStyleHint(styleHint));
426     result.append(QWindowsFontDatabase::extraTryFontsForFamily(family));
427     result.append(QFreeTypeFontDatabase::fallbacksForFamily(family, style, styleHint, script));
428 
429     qCDebug(lcQpaFonts) << __FUNCTION__ << family << style << styleHint
430         << script << result;
431 
432     return result;
433 }
fontDir() const434 QString QWindowsFontDatabaseFT::fontDir() const
435 {
436     const QString result = QLatin1String(qgetenv("windir")) + QLatin1String("/Fonts");//QPlatformFontDatabase::fontDir();
437     qCDebug(lcQpaFonts) << __FUNCTION__ << result;
438     return result;
439 }
440 
defaultFont() const441 QFont QWindowsFontDatabaseFT::defaultFont() const
442 {
443     return QWindowsFontDatabase::systemDefaultFont();
444 }
445 
446 QT_END_NAMESPACE
447