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 "qwinrtfontdatabase_p.h"
41 
42 #include <QtFontDatabaseSupport/private/qfontengine_ft_p.h>
43 
44 #include <QtCore/QCoreApplication>
45 #include <QtCore/QFile>
46 
47 #include <QtCore/QUuid>
48 #include <dwrite_1.h>
49 #include <wrl.h>
50 using namespace Microsoft::WRL;
51 
52 QT_BEGIN_NAMESPACE
53 
54 Q_LOGGING_CATEGORY(lcQpaFonts, "qt.qpa.fonts")
55 
operator <<(QDebug d,const QFontDef & def)56 QDebug operator<<(QDebug d, const QFontDef &def)
57 {
58     QDebugStateSaver saver(d);
59     d.nospace();
60     d << "Family=" << def.family << " Stylename=" << def.styleName
61         << " pointsize=" << def.pointSize << " pixelsize=" << def.pixelSize
62         << " styleHint=" << def.styleHint << " weight=" << def.weight
63         << " stretch=" << def.stretch << " hintingPreference="
64         << def.hintingPreference;
65     return d;
66 }
67 
68 // Based on unicode range tables at http://www.microsoft.com/typography/otspec/os2.htm#ur
writingSystemFromUnicodeRange(const DWRITE_UNICODE_RANGE & range)69 static QFontDatabase::WritingSystem writingSystemFromUnicodeRange(const DWRITE_UNICODE_RANGE &range)
70 {
71     if (range.first >= 0x0000 && range.last <= 0x007F)
72         return QFontDatabase::Latin;
73     if (range.first >= 0x0370 && range.last <= 0x03FF)
74         return QFontDatabase::Greek;
75     if (range.first >= 0x0400 && range.last <= 0x04FF)
76         return QFontDatabase::Cyrillic;
77     if (range.first >= 0x0530 && range.last <= 0x058F)
78         return QFontDatabase::Armenian;
79     if (range.first >= 0x0590 && range.last <= 0x05FF)
80         return QFontDatabase::Hebrew;
81     if (range.first >= 0x0600 && range.last <= 0x06FF)
82         return QFontDatabase::Arabic;
83     if (range.first >= 0x0700 && range.last <= 0x074F)
84         return QFontDatabase::Syriac;
85     if (range.first >= 0x0780 && range.last <= 0x07BF)
86         return QFontDatabase::Thaana;
87     if (range.first >= 0x0900 && range.last <= 0x097F)
88         return QFontDatabase::Devanagari;
89     if (range.first >= 0x0980 && range.last <= 0x09FF)
90         return QFontDatabase::Bengali;
91     if (range.first >= 0x0A00 && range.last <= 0x0A7F)
92         return QFontDatabase::Gurmukhi;
93     if (range.first >= 0x0A80 && range.last <= 0x0AFF)
94         return QFontDatabase::Gujarati;
95     if (range.first >= 0x0B00 && range.last <= 0x0B7F)
96         return QFontDatabase::Oriya;
97     if (range.first >= 0x0B80 && range.last <= 0x0BFF)
98         return QFontDatabase::Tamil;
99     if (range.first >= 0x0C00 && range.last <= 0x0C7F)
100         return QFontDatabase::Telugu;
101     if (range.first >= 0x0C80 && range.last <= 0x0CFF)
102         return QFontDatabase::Kannada;
103     if (range.first >= 0x0D00 && range.last <= 0x0D7F)
104         return QFontDatabase::Malayalam;
105     if (range.first >= 0x0D80 && range.last <= 0x0DFF)
106         return QFontDatabase::Sinhala;
107     if (range.first >= 0x0E00 && range.last <= 0x0E7F)
108         return QFontDatabase::Thai;
109     if (range.first >= 0x0E80 && range.last <= 0x0EFF)
110         return QFontDatabase::Lao;
111     if (range.first >= 0x0F00 && range.last <= 0x0FFF)
112         return QFontDatabase::Tibetan;
113     if (range.first >= 0x1000 && range.last <= 0x109F)
114         return QFontDatabase::Myanmar;
115     if (range.first >= 0x10A0 && range.last <= 0x10FF)
116         return QFontDatabase::Georgian;
117     if (range.first >= 0x1780 && range.last <= 0x17FF)
118         return QFontDatabase::Khmer;
119     if (range.first >= 0x4E00 && range.last <= 0x9FFF)
120         return QFontDatabase::SimplifiedChinese;
121     if (range.first >= 0xAC00 && range.last <= 0xD7AF)
122         return QFontDatabase::Korean;
123     if (range.first >= 0x1680 && range.last <= 0x169F)
124         return QFontDatabase::Ogham;
125     if (range.first >= 0x16A0 && range.last <= 0x16FF)
126         return QFontDatabase::Runic;
127     if (range.first >= 0x07C0 && range.last <= 0x07FF)
128         return QFontDatabase::Nko;
129 
130     return QFontDatabase::Other;
131 }
132 
~QWinRTFontDatabase()133 QWinRTFontDatabase::~QWinRTFontDatabase()
134 {
135     qCDebug(lcQpaFonts) << __FUNCTION__;
136 
137     foreach (IDWriteFontFile *fontFile, m_fonts.keys())
138         fontFile->Release();
139 
140     foreach (IDWriteFontFamily *fontFamily, m_fontFamilies)
141         fontFamily->Release();
142 }
143 
fontDir() const144 QString QWinRTFontDatabase::fontDir() const
145 {
146     qCDebug(lcQpaFonts) << __FUNCTION__;
147     QString fontDirectory = QFreeTypeFontDatabase::fontDir();
148     if (!QFile::exists(fontDirectory)) {
149         // Fall back to app directory + fonts, and just app directory after that
150         const QString applicationDirPath = QCoreApplication::applicationDirPath();
151         fontDirectory = applicationDirPath + QLatin1String("/fonts");
152         if (!QFile::exists(fontDirectory)) {
153             if (m_fontFamilies.isEmpty())
154                 qWarning("No fonts directory found in application package.");
155             fontDirectory = applicationDirPath;
156         }
157     }
158     return fontDirectory;
159 }
160 
defaultFont() const161 QFont QWinRTFontDatabase::defaultFont() const
162 {
163     return QFont(QStringLiteral("Segoe UI"));
164 }
165 
fontsAlwaysScalable() const166 bool QWinRTFontDatabase::fontsAlwaysScalable() const
167 {
168     return true;
169 }
170 
populateFontDatabase()171 void QWinRTFontDatabase::populateFontDatabase()
172 {
173     qCDebug(lcQpaFonts) << __FUNCTION__;
174 
175     ComPtr<IDWriteFactory1> factory;
176     HRESULT hr = DWriteCreateFactory(DWRITE_FACTORY_TYPE_ISOLATED, __uuidof(IDWriteFactory1), &factory);
177     if (FAILED(hr)) {
178         qWarning("Failed to create DirectWrite factory: %s", qPrintable(qt_error_string(hr)));
179         QFreeTypeFontDatabase::populateFontDatabase();
180         return;
181     }
182 
183     ComPtr<IDWriteFontCollection> fontCollection;
184     hr = factory->GetSystemFontCollection(&fontCollection);
185     if (FAILED(hr)) {
186         qWarning("Failed to open system font collection: %s", qPrintable(qt_error_string(hr)));
187         QFreeTypeFontDatabase::populateFontDatabase();
188         return;
189     }
190 
191     int fontFamilyCount = fontCollection->GetFontFamilyCount();
192     for (int i = 0; i < fontFamilyCount; ++i) {
193         ComPtr<IDWriteFontFamily> fontFamily;
194         hr = fontCollection->GetFontFamily(i, &fontFamily);
195         if (FAILED(hr)) {
196             qWarning("Unable to get font family: %s", qPrintable(qt_error_string(hr)));
197             continue;
198         }
199 
200         ComPtr<IDWriteLocalizedStrings> names;
201         hr = fontFamily->GetFamilyNames(&names);
202         if (FAILED(hr)) {
203             qWarning("Unable to get font family names: %s", qPrintable(qt_error_string(hr)));
204             continue;
205         }
206         quint32 familyNameLength;
207         hr = names->GetStringLength(0, &familyNameLength);
208         if (FAILED(hr)) {
209             qWarning("Unable to get family name length: %s", qPrintable(qt_error_string(hr)));
210             continue;
211         }
212         QVector<wchar_t> familyBuffer(familyNameLength + 1);
213         hr = names->GetString(0, familyBuffer.data(), familyBuffer.size());
214         if (FAILED(hr)) {
215             qWarning("Unable to create font family name: %s", qPrintable(qt_error_string(hr)));
216             continue;
217         }
218         QString familyName = QString::fromWCharArray(familyBuffer.data(), familyNameLength);
219 
220         m_fontFamilies.insert(familyName, fontFamily.Detach());
221 
222         registerFontFamily(familyName);
223     }
224 
225     QFreeTypeFontDatabase::populateFontDatabase();
226 }
227 
populateFamily(const QString & familyName)228 void QWinRTFontDatabase::populateFamily(const QString &familyName)
229 {
230     qCDebug(lcQpaFonts) << __FUNCTION__ << familyName;
231 
232     IDWriteFontFamily *fontFamily = m_fontFamilies.value(familyName);
233     if (!fontFamily) {
234         qWarning("The font family %s was not found.", qPrintable(familyName));
235         return;
236     }
237 
238     bool fontRegistered = false;
239     const int fontCount = fontFamily->GetFontCount();
240     for (int j = 0; j < fontCount; ++j) {
241         ComPtr<IDWriteFont> font;
242         HRESULT hr = fontFamily->GetFont(j, &font);
243         if (FAILED(hr)) {
244             qWarning("Unable to get font: %s", qPrintable(qt_error_string(hr)));
245             continue;
246         }
247 
248         // Skip simulated faces
249         if (font->GetSimulations() != DWRITE_FONT_SIMULATIONS_NONE)
250             continue;
251 
252         ComPtr<IDWriteFontFace> baseFontFace;
253         hr = font->CreateFontFace(&baseFontFace);
254         if (FAILED(hr)) {
255             qWarning("Unable to create base font face: %s", qPrintable(qt_error_string(hr)));
256             continue;
257         }
258         ComPtr<IDWriteFontFace1> fontFace;
259         hr = baseFontFace.As(&fontFace);
260         if (FAILED(hr)) {
261             qWarning("Unable to create font face: %s", qPrintable(qt_error_string(hr)));
262             continue;
263         }
264 
265         // We can't deal with multi-file fonts
266         quint32 fileCount;
267         hr = fontFace->GetFiles(&fileCount, NULL);
268         if (FAILED(hr)) {
269             qWarning("Unable to get font file count: %s", qPrintable(qt_error_string(hr)));
270             continue;
271         }
272         if (fileCount != 1)
273             continue;
274 
275         ComPtr<IDWriteLocalizedStrings> informationalStrings;
276         BOOL exists;
277         hr = font->GetInformationalStrings(DWRITE_INFORMATIONAL_STRING_MANUFACTURER,
278                                            &informationalStrings, &exists);
279         if (FAILED(hr)) {
280             qWarning("Unable to get font foundry: %s", qPrintable(qt_error_string(hr)));
281             continue;
282         }
283         QString foundryName;
284         if (exists) {
285             quint32 length;
286             hr = informationalStrings->GetStringLength(0, &length);
287             if (FAILED(hr))
288                 qWarning("Unable to get foundry name length: %s", qPrintable(qt_error_string(hr)));
289             if (SUCCEEDED(hr)) {
290                 QVector<wchar_t> buffer(length + 1);
291                 hr = informationalStrings->GetString(0, buffer.data(), buffer.size());
292                 if (FAILED(hr))
293                     qWarning("Unable to get foundry name: %s", qPrintable(qt_error_string(hr)));
294                 if (SUCCEEDED(hr))
295                     foundryName = QString::fromWCharArray(buffer.data(), length);
296             }
297         }
298 
299         QFont::Weight weight = QPlatformFontDatabase::weightFromInteger(font->GetWeight());
300 
301         QFont::Style style;
302         switch (font->GetStyle()) {
303         default:
304         case DWRITE_FONT_STYLE_NORMAL:
305             style = QFont::StyleNormal;
306             break;
307         case DWRITE_FONT_STYLE_OBLIQUE:
308             style = QFont::StyleOblique;
309             break;
310         case DWRITE_FONT_STYLE_ITALIC:
311             style = QFont::StyleItalic;
312             break;
313         }
314 
315         QFont::Stretch stretch;
316         switch (font->GetStretch()) {
317         default:
318         case DWRITE_FONT_STRETCH_UNDEFINED:
319         case DWRITE_FONT_STRETCH_NORMAL:
320             stretch = QFont::Unstretched;
321             break;
322         case DWRITE_FONT_STRETCH_ULTRA_CONDENSED:
323             stretch = QFont::UltraCondensed;
324             break;
325         case DWRITE_FONT_STRETCH_EXTRA_CONDENSED:
326             stretch = QFont::ExtraCondensed;
327             break;
328         case DWRITE_FONT_STRETCH_CONDENSED:
329             stretch = QFont::Condensed;
330             break;
331         case DWRITE_FONT_STRETCH_SEMI_CONDENSED:
332             stretch = QFont::SemiCondensed;
333             break;
334         case DWRITE_FONT_STRETCH_SEMI_EXPANDED:
335             stretch = QFont::SemiExpanded;
336             break;
337         case DWRITE_FONT_STRETCH_EXPANDED:
338             stretch = QFont::Expanded;
339             break;
340         case DWRITE_FONT_STRETCH_EXTRA_EXPANDED:
341             stretch = QFont::ExtraExpanded;
342             break;
343         case DWRITE_FONT_STRETCH_ULTRA_EXPANDED:
344             stretch = QFont::UltraExpanded;
345             break;
346         }
347 
348         const bool fixedPitch = fontFace->IsMonospacedFont();
349 
350         // Get writing systems from unicode ranges
351         quint32 actualRangeCount;
352         hr = fontFace->GetUnicodeRanges(0, nullptr, &actualRangeCount);
353         Q_ASSERT(hr == E_NOT_SUFFICIENT_BUFFER);
354         QVector<DWRITE_UNICODE_RANGE> unicodeRanges(actualRangeCount);
355         hr = fontFace->GetUnicodeRanges(actualRangeCount, unicodeRanges.data(), &actualRangeCount);
356         if (FAILED(hr)) {
357             qWarning("Unable to get font unicode range: %s", qPrintable(qt_error_string(hr)));
358             continue;
359         }
360         QSupportedWritingSystems writingSystems;
361         for (quint32 i = 0; i < actualRangeCount; ++i) {
362             const QFontDatabase::WritingSystem writingSystem = writingSystemFromUnicodeRange(unicodeRanges.at(i));
363             writingSystems.setSupported(writingSystem);
364         }
365         if (writingSystems.supported(QFontDatabase::SimplifiedChinese)) {
366             writingSystems.setSupported(QFontDatabase::TraditionalChinese);
367             writingSystems.setSupported(QFontDatabase::Japanese);
368         }
369         if (writingSystems.supported(QFontDatabase::Latin))
370             writingSystems.setSupported(QFontDatabase::Vietnamese);
371 
372         IDWriteFontFile *fontFile;
373         hr = fontFace->GetFiles(&fileCount, &fontFile);
374         if (FAILED(hr)) {
375             qWarning("Unable to get font file: %s", qPrintable(qt_error_string(hr)));
376             continue;
377         }
378 
379         FontDescription description = { fontFace->GetIndex(), QUuid::createUuid().toByteArray() };
380         m_fonts.insert(fontFile, description);
381         registerFont(familyName, QString(), foundryName, weight, style, stretch,
382                      true, true, 0, fixedPitch, writingSystems, fontFile);
383         fontRegistered = true;
384     }
385 
386     // Always populate something to avoid an assert
387     if (!fontRegistered) {
388         registerFont(familyName, QString(), QString(), QFont::Normal, QFont::StyleNormal,
389                      QFont::Unstretched, false, false, 0, false, QSupportedWritingSystems(), 0);
390     }
391 }
392 
fontEngine(const QFontDef & fontDef,void * handle)393 QFontEngine *QWinRTFontDatabase::fontEngine(const QFontDef &fontDef, void *handle)
394 {
395     qCDebug(lcQpaFonts) << __FUNCTION__ << "FONTDEF" << fontDef << handle;
396 
397     if (!handle) // Happens if a font family population failed
398         return 0;
399 
400     IDWriteFontFile *fontFile = reinterpret_cast<IDWriteFontFile *>(handle);
401     if (!m_fonts.contains(fontFile))
402         return QFreeTypeFontDatabase::fontEngine(fontDef, handle);
403 
404     const void *referenceKey;
405     quint32 referenceKeySize;
406     HRESULT hr = fontFile->GetReferenceKey(&referenceKey, &referenceKeySize);
407     if (FAILED(hr)) {
408         qWarning("Unable to get font file reference key: %s", qPrintable(qt_error_string(hr)));
409         return 0;
410     }
411 
412     ComPtr<IDWriteFontFileLoader> loader;
413     hr = fontFile->GetLoader(&loader);
414     if (FAILED(hr)) {
415         qWarning("Unable to get font file loader: %s", qPrintable(qt_error_string(hr)));
416         return 0;
417     }
418 
419     ComPtr<IDWriteFontFileStream> stream;
420     hr =loader->CreateStreamFromKey(referenceKey, referenceKeySize, &stream);
421     if (FAILED(hr)) {
422         qWarning("Unable to get font file stream: %s", qPrintable(qt_error_string(hr)));
423         return 0;
424     }
425 
426     quint64 fileSize;
427     hr = stream->GetFileSize(&fileSize);
428     if (FAILED(hr)) {
429         qWarning("Unable to get font file size: %s", qPrintable(qt_error_string(hr)));
430         return 0;
431     }
432 
433     const void *data;
434     void *context;
435     hr = stream->ReadFileFragment(&data, 0, fileSize, &context);
436     if (FAILED(hr)) {
437         qWarning("Unable to get font file data: %s", qPrintable(qt_error_string(hr)));
438         return 0;
439     }
440     const QByteArray fontData((const char *)data, fileSize);
441     stream->ReleaseFileFragment(context);
442 
443     QFontEngine::FaceId faceId;
444     const FontDescription description = m_fonts.value(fontFile);
445     faceId.uuid = description.uuid;
446     faceId.index = description.index;
447 
448     return QFontEngineFT::create(fontDef, faceId, fontData);
449 }
450 
familyForStyleHint(QFont::StyleHint styleHint)451 QString QWinRTFontDatabase::familyForStyleHint(QFont::StyleHint styleHint)
452 {
453     switch (styleHint) {
454     case QFont::Times:
455         return QStringLiteral("Times New Roman");
456     case QFont::Courier:
457         return QStringLiteral("Courier New");
458     case QFont::Monospace:
459         return QStringLiteral("Courier New");
460     case QFont::Cursive:
461         return QStringLiteral("Comic Sans MS");
462     case QFont::Fantasy:
463         return QStringLiteral("Impact");
464     case QFont::Decorative:
465         return QStringLiteral("Old English");
466     case QFont::Helvetica:
467         return QStringLiteral("Segoe UI");
468     case QFont::System:
469     default:
470         break;
471     }
472     return QStringLiteral("Segoe UI");
473 }
474 
fallbacksForFamily(const QString & family,QFont::Style style,QFont::StyleHint styleHint,QChar::Script script) const475 QStringList QWinRTFontDatabase::fallbacksForFamily(const QString &family, QFont::Style style,
476                                                    QFont::StyleHint styleHint,
477                                                    QChar::Script script) const
478 {
479     Q_UNUSED(style)
480     Q_UNUSED(script)
481 
482     qCDebug(lcQpaFonts) << __FUNCTION__ << family;
483 
484     QStringList result;
485     result.append(QWinRTFontDatabase::familyForStyleHint(styleHint));
486     result.append(QFreeTypeFontDatabase::fallbacksForFamily(family, style, styleHint, script));
487     return result;
488 }
489 
releaseHandle(void * handle)490 void QWinRTFontDatabase::releaseHandle(void *handle)
491 {
492     qCDebug(lcQpaFonts) << __FUNCTION__ << handle;
493 
494     if (!handle)
495         return;
496 
497     IDWriteFontFile *fontFile = reinterpret_cast<IDWriteFontFile *>(handle);
498     if (m_fonts.contains(fontFile)) {
499         m_fonts.remove(fontFile);
500         fontFile->Release();
501         return;
502     }
503 
504     QFreeTypeFontDatabase::releaseHandle(handle);
505 }
506 
507 QT_END_NAMESPACE
508