1 | // Copyright (C) 2016 The Qt Company Ltd. |
2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only |
3 | |
4 | #include "qfreetypefontdatabase_p.h" |
5 | |
6 | #include <QtGui/private/qguiapplication_p.h> |
7 | #include <qpa/qplatformscreen.h> |
8 | |
9 | #include <QtCore/QFile> |
10 | #include <QtCore/QLibraryInfo> |
11 | #include <QtCore/QDir> |
12 | #include <QtCore/QtEndian> |
13 | #include <QtCore/QLoggingCategory> |
14 | #include <QtCore/QUuid> |
15 | |
16 | #undef QT_NO_FREETYPE |
17 | #include "qfontengine_ft_p.h" |
18 | |
19 | #include <ft2build.h> |
20 | #include FT_TRUETYPE_TABLES_H |
21 | #include FT_ERRORS_H |
22 | |
23 | #include FT_MULTIPLE_MASTERS_H |
24 | #include FT_SFNT_NAMES_H |
25 | #include FT_TRUETYPE_IDS_H |
26 | |
27 | QT_BEGIN_NAMESPACE |
28 | |
29 | Q_DECLARE_LOGGING_CATEGORY(lcFontDb) |
30 | |
31 | using namespace Qt::StringLiterals; |
32 | |
33 | void QFreeTypeFontDatabase::populateFontDatabase() |
34 | { |
35 | QString fontpath = fontDir(); |
36 | QDir dir(fontpath); |
37 | |
38 | if (!dir.exists()) { |
39 | qWarning(msg: "QFontDatabase: Cannot find font directory %s.\n" |
40 | "Note that Qt no longer ships fonts. Deploy some (from https://dejavu-fonts.github.io/ for example) or switch to fontconfig." , |
41 | qPrintable(fontpath)); |
42 | return; |
43 | } |
44 | |
45 | static const QString nameFilters[] = { |
46 | u"*.ttf"_s , |
47 | u"*.pfa"_s , |
48 | u"*.pfb"_s , |
49 | u"*.otf"_s , |
50 | }; |
51 | |
52 | const auto fis = dir.entryInfoList(nameFilters: QStringList::fromReadOnlyData(t: nameFilters), filters: QDir::Files); |
53 | for (const QFileInfo &fi : fis) { |
54 | const QByteArray file = QFile::encodeName(fileName: fi.absoluteFilePath()); |
55 | QFreeTypeFontDatabase::addTTFile(fontData: QByteArray(), file); |
56 | } |
57 | } |
58 | |
59 | QFontEngine *QFreeTypeFontDatabase::fontEngine(const QFontDef &fontDef, void *usrPtr) |
60 | { |
61 | FontFile *fontfile = static_cast<FontFile *>(usrPtr); |
62 | QFontEngine::FaceId faceId; |
63 | faceId.filename = QFile::encodeName(fileName: fontfile->fileName); |
64 | faceId.index = fontfile->indexValue; |
65 | faceId.instanceIndex = fontfile->instanceIndex; |
66 | faceId.variableAxes = fontDef.variableAxisValues; |
67 | |
68 | // Make sure the FaceId compares uniquely in cases where a |
69 | // file name is not provided. |
70 | if (faceId.filename.isEmpty()) { |
71 | QUuid::Id128Bytes id{}; |
72 | memcpy(dest: &id, src: &usrPtr, n: sizeof(usrPtr)); |
73 | faceId.uuid = QUuid(id).toByteArray(); |
74 | } |
75 | |
76 | return QFontEngineFT::create(fontDef, faceId, fontData: fontfile->data); |
77 | } |
78 | |
79 | QFontEngine *QFreeTypeFontDatabase::fontEngine(const QByteArray &fontData, qreal pixelSize, |
80 | QFont::HintingPreference hintingPreference) |
81 | { |
82 | return QFontEngineFT::create(fontData, pixelSize, hintingPreference, variableAxisValue: {}); |
83 | } |
84 | |
85 | QStringList QFreeTypeFontDatabase::addApplicationFont(const QByteArray &fontData, const QString &fileName, QFontDatabasePrivate::ApplicationFont *applicationFont) |
86 | { |
87 | return QFreeTypeFontDatabase::addTTFile(fontData, file: fileName.toLocal8Bit(), applicationFont); |
88 | } |
89 | |
90 | void QFreeTypeFontDatabase::releaseHandle(void *handle) |
91 | { |
92 | FontFile *file = static_cast<FontFile *>(handle); |
93 | delete file; |
94 | } |
95 | |
96 | extern FT_Library qt_getFreetype(); |
97 | |
98 | void QFreeTypeFontDatabase::addNamedInstancesForFace(void *face_, |
99 | int faceIndex, |
100 | const QString &family, |
101 | const QString &styleName, |
102 | QFont::Weight weight, |
103 | QFont::Stretch stretch, |
104 | QFont::Style style, |
105 | bool fixedPitch, |
106 | const QSupportedWritingSystems &writingSystems, |
107 | const QByteArray &fileName, |
108 | const QByteArray &fontData) |
109 | { |
110 | FT_Face face = reinterpret_cast<FT_Face>(face_); |
111 | |
112 | // Note: The following does not actually depend on API from 2.9, but the |
113 | // FT_Set_Named_Instance() was added in 2.9, so to avoid populating the database with |
114 | // named instances that cannot be selected, we disable the feature on older Freetype |
115 | // versions. |
116 | #if (FREETYPE_MAJOR*10000 + FREETYPE_MINOR*100 + FREETYPE_PATCH) >= 20900 |
117 | FT_MM_Var *var = nullptr; |
118 | FT_Get_MM_Var(face, amaster: &var); |
119 | if (var != nullptr) { |
120 | std::unique_ptr<FT_MM_Var, void(*)(FT_MM_Var*)> varGuard(var, [](FT_MM_Var *res) { |
121 | FT_Done_MM_Var(library: qt_getFreetype(), amaster: res); |
122 | }); |
123 | |
124 | for (FT_UInt i = 0; i < var->num_namedstyles; ++i) { |
125 | FT_UInt id = var->namedstyle[i].strid; |
126 | |
127 | QFont::Weight instanceWeight = weight; |
128 | QFont::Stretch instanceStretch = stretch; |
129 | QFont::Style instanceStyle = style; |
130 | for (FT_UInt axis = 0; axis < var->num_axis; ++axis) { |
131 | if (var->axis[axis].tag == QFont::Tag("wght" ).value()) { |
132 | instanceWeight = QFont::Weight(var->namedstyle[i].coords[axis] >> 16); |
133 | } else if (var->axis[axis].tag == QFont::Tag("wdth" ).value()) { |
134 | instanceStretch = QFont::Stretch(var->namedstyle[i].coords[axis] >> 16); |
135 | } else if (var->axis[axis].tag == QFont::Tag("ital" ).value()) { |
136 | FT_UInt ital = var->namedstyle[i].coords[axis] >> 16; |
137 | if (ital == 1) |
138 | instanceStyle = QFont::StyleItalic; |
139 | else |
140 | instanceStyle = QFont::StyleNormal; |
141 | } |
142 | } |
143 | |
144 | FT_UInt count = FT_Get_Sfnt_Name_Count(face); |
145 | for (FT_UInt j = 0; j < count; ++j) { |
146 | FT_SfntName name; |
147 | if (FT_Get_Sfnt_Name(face, idx: j, aname: &name)) |
148 | continue; |
149 | |
150 | if (name.name_id != id) |
151 | continue; |
152 | |
153 | // Only support Unicode for now |
154 | if (name.encoding_id != TT_MS_ID_UNICODE_CS) |
155 | continue; |
156 | |
157 | // Sfnt names stored as UTF-16BE |
158 | QString instanceName; |
159 | for (FT_UInt k = 0; k < name.string_len; k += 2) |
160 | instanceName += QChar((name.string[k] << 8) + name.string[k + 1]); |
161 | if (instanceName != styleName) { |
162 | FontFile *variantFontFile = new FontFile{ |
163 | .fileName: QFile::decodeName(localFileName: fileName), |
164 | .indexValue: faceIndex, |
165 | .instanceIndex: int(i), |
166 | .data: fontData |
167 | }; |
168 | |
169 | qCDebug(lcFontDb) << "Registering named instance" << i |
170 | << ":" << instanceName |
171 | << "for font family" << family |
172 | << "with weight" << instanceWeight |
173 | << ", style" << instanceStyle |
174 | << ", stretch" << instanceStretch; |
175 | |
176 | registerFont(familyname: family, |
177 | stylename: instanceName, |
178 | foundryname: QString(), |
179 | weight: instanceWeight, |
180 | style: instanceStyle, |
181 | stretch: instanceStretch, |
182 | antialiased: true, |
183 | scalable: true, |
184 | pixelSize: 0, |
185 | fixedPitch, |
186 | writingSystems, |
187 | handle: variantFontFile); |
188 | } |
189 | } |
190 | } |
191 | } |
192 | #else |
193 | Q_UNUSED(face); |
194 | Q_UNUSED(family); |
195 | Q_UNUSED(styleName); |
196 | Q_UNUSED(weight); |
197 | Q_UNUSED(stretch); |
198 | Q_UNUSED(style); |
199 | Q_UNUSED(fixedPitch); |
200 | Q_UNUSED(writingSystems); |
201 | Q_UNUSED(fontData); |
202 | #endif |
203 | |
204 | } |
205 | |
206 | QStringList QFreeTypeFontDatabase::addTTFile(const QByteArray &fontData, const QByteArray &file, QFontDatabasePrivate::ApplicationFont *applicationFont) |
207 | { |
208 | FT_Library library = qt_getFreetype(); |
209 | |
210 | int index = 0; |
211 | int numFaces = 0; |
212 | QStringList families; |
213 | do { |
214 | FT_Face face; |
215 | FT_Error error; |
216 | if (!fontData.isEmpty()) { |
217 | error = FT_New_Memory_Face(library, file_base: (const FT_Byte *)fontData.constData(), file_size: fontData.size(), face_index: index, aface: &face); |
218 | } else { |
219 | error = FT_New_Face(library, filepathname: file.constData(), face_index: index, aface: &face); |
220 | } |
221 | if (error != FT_Err_Ok) { |
222 | qDebug() << "FT_New_Face failed with index" << index << ':' << Qt::hex << error; |
223 | break; |
224 | } |
225 | numFaces = face->num_faces; |
226 | |
227 | QFont::Weight weight = QFont::Normal; |
228 | |
229 | QFont::Style style = QFont::StyleNormal; |
230 | if (face->style_flags & FT_STYLE_FLAG_ITALIC) |
231 | style = QFont::StyleItalic; |
232 | |
233 | if (face->style_flags & FT_STYLE_FLAG_BOLD) |
234 | weight = QFont::Bold; |
235 | |
236 | bool fixedPitch = (face->face_flags & FT_FACE_FLAG_FIXED_WIDTH); |
237 | QSupportedWritingSystems writingSystems; |
238 | // detect symbol fonts |
239 | for (int i = 0; i < face->num_charmaps; ++i) { |
240 | FT_CharMap cm = face->charmaps[i]; |
241 | if (cm->encoding == FT_ENCODING_ADOBE_CUSTOM |
242 | || cm->encoding == FT_ENCODING_MS_SYMBOL) { |
243 | writingSystems.setSupported(QFontDatabase::Symbol); |
244 | break; |
245 | } |
246 | } |
247 | |
248 | QFont::Stretch stretch = QFont::Unstretched; |
249 | TT_OS2 *os2 = (TT_OS2 *)FT_Get_Sfnt_Table(face, ft_sfnt_os2); |
250 | if (os2) { |
251 | quint32 unicodeRange[4] = { |
252 | quint32(os2->ulUnicodeRange1), |
253 | quint32(os2->ulUnicodeRange2), |
254 | quint32(os2->ulUnicodeRange3), |
255 | quint32(os2->ulUnicodeRange4) |
256 | }; |
257 | quint32 [2] = { |
258 | quint32(os2->ulCodePageRange1), |
259 | quint32(os2->ulCodePageRange2) |
260 | }; |
261 | |
262 | writingSystems = QPlatformFontDatabase::writingSystemsFromTrueTypeBits(unicodeRange, codePageRange); |
263 | |
264 | if (os2->usWeightClass) { |
265 | weight = static_cast<QFont::Weight>(os2->usWeightClass); |
266 | } else if (os2->panose[2]) { |
267 | int w = os2->panose[2]; |
268 | if (w <= 1) |
269 | weight = QFont::Thin; |
270 | else if (w <= 2) |
271 | weight = QFont::ExtraLight; |
272 | else if (w <= 3) |
273 | weight = QFont::Light; |
274 | else if (w <= 5) |
275 | weight = QFont::Normal; |
276 | else if (w <= 6) |
277 | weight = QFont::Medium; |
278 | else if (w <= 7) |
279 | weight = QFont::DemiBold; |
280 | else if (w <= 8) |
281 | weight = QFont::Bold; |
282 | else if (w <= 9) |
283 | weight = QFont::ExtraBold; |
284 | else if (w <= 10) |
285 | weight = QFont::Black; |
286 | } |
287 | |
288 | switch (os2->usWidthClass) { |
289 | case 1: |
290 | stretch = QFont::UltraCondensed; |
291 | break; |
292 | case 2: |
293 | stretch = QFont::ExtraCondensed; |
294 | break; |
295 | case 3: |
296 | stretch = QFont::Condensed; |
297 | break; |
298 | case 4: |
299 | stretch = QFont::SemiCondensed; |
300 | break; |
301 | case 5: |
302 | stretch = QFont::Unstretched; |
303 | break; |
304 | case 6: |
305 | stretch = QFont::SemiExpanded; |
306 | break; |
307 | case 7: |
308 | stretch = QFont::Expanded; |
309 | break; |
310 | case 8: |
311 | stretch = QFont::ExtraExpanded; |
312 | break; |
313 | case 9: |
314 | stretch = QFont::UltraExpanded; |
315 | break; |
316 | } |
317 | } |
318 | |
319 | QString family = QString::fromLatin1(ba: face->family_name); |
320 | FontFile *fontFile = new FontFile{ |
321 | .fileName: QFile::decodeName(localFileName: file), |
322 | .indexValue: index, |
323 | .instanceIndex: -1, |
324 | .data: fontData |
325 | }; |
326 | |
327 | QString styleName = QString::fromLatin1(ba: face->style_name); |
328 | |
329 | if (applicationFont != nullptr) { |
330 | QFontDatabasePrivate::ApplicationFont::Properties properties; |
331 | properties.familyName = family; |
332 | properties.styleName = styleName; |
333 | properties.weight = weight; |
334 | properties.stretch = stretch; |
335 | properties.style = style; |
336 | |
337 | applicationFont->properties.append(t: properties); |
338 | } |
339 | |
340 | registerFont(familyname: family, stylename: styleName, foundryname: QString(), weight, style, stretch, antialiased: true, scalable: true, pixelSize: 0, fixedPitch, writingSystems, handle: fontFile); |
341 | |
342 | addNamedInstancesForFace(face_: face, faceIndex: index, family, styleName, weight, stretch, style, fixedPitch, writingSystems, fileName: file, fontData); |
343 | |
344 | families.append(t: family); |
345 | |
346 | FT_Done_Face(face); |
347 | ++index; |
348 | } while (index < numFaces); |
349 | return families; |
350 | } |
351 | |
352 | bool QFreeTypeFontDatabase::supportsVariableApplicationFonts() const |
353 | { |
354 | #if (FREETYPE_MAJOR*10000 + FREETYPE_MINOR*100 + FREETYPE_PATCH) >= 20900 |
355 | return true; |
356 | #else |
357 | return false; |
358 | #endif |
359 | } |
360 | |
361 | QT_END_NAMESPACE |
362 | |