Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qfontengine_ft.cpp
Go to the documentation of this file.
1// Copyright (C) 2021 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// Qt-Security score:critical reason:data-parser
4
5#include "qdir.h"
6#include "qmetatype.h"
7#include "qtextstream.h"
8#include "qvariant.h"
10#include "private/qfontdatabase_p.h"
11#include "private/qimage_p.h"
12#include <private/qstringiterator_p.h>
13#include <qguiapplication.h>
14#include <qscreen.h>
15#include <qpa/qplatformscreen.h>
16#include <QtCore/QUuid>
17#include <QtCore/QLoggingCategory>
18#include <QtGui/QPainterPath>
19
20#ifndef QT_NO_FREETYPE
21
22#include "qfile.h"
23#include "qfileinfo.h"
24#include <qscopedvaluerollback.h>
25#include "qthreadstorage.h"
26#include <qmath.h>
27#include <qendian.h>
28#include <private/qcolrpaintgraphrenderer_p.h>
29
30#include <memory>
31
32#include <ft2build.h>
33#include FT_FREETYPE_H
34#include FT_OUTLINE_H
35#include FT_SYNTHESIS_H
36#include FT_TRUETYPE_TABLES_H
37#include FT_TYPE1_TABLES_H
38#include FT_GLYPH_H
39#include FT_MODULE_H
40#include FT_LCD_FILTER_H
41#include FT_MULTIPLE_MASTERS_H
42
43#if defined(FT_CONFIG_OPTIONS_H)
44#include FT_CONFIG_OPTIONS_H
45#endif
46
47#if defined(FT_FONT_FORMATS_H)
48#include FT_FONT_FORMATS_H
49#endif
50
51#ifdef QT_LINUXBASE
52#include FT_ERRORS_H
53#endif
54
56
57using namespace Qt::StringLiterals;
58
59#define FLOOR(x) ((x) & -64)
60#define CEIL(x) (((x)+63) & -64)
61#define TRUNC(x) ((x) >> 6)
62#define ROUND(x) (((x)+32) & -64)
63
64static bool ft_getSfntTable(void *user_data, uint tag, uchar *buffer, uint *length)
65{
66 FT_Face face = (FT_Face)user_data;
67
68 bool result = false;
69 if (FT_IS_SFNT(face)) {
70 FT_ULong len = *length;
71 result = FT_Load_Sfnt_Table(face, tag, 0, buffer, &len) == FT_Err_Ok
72 && len > 0;
73 *length = len;
74 }
75
76 return result;
77}
78
80
82#ifdef Q_OS_WIN
83 QFontEngineFT::HintFull;
84#else
85 QFontEngineFT::HintNone;
86#endif
87
88// -------------------------- Freetype support ------------------------------
89
91{
92public:
94 : library(nullptr)
95 { }
97
98 struct FaceStyle {
99 QString faceFileName;
100 QString styleName;
101
102 FaceStyle(QString faceFileName, QString styleName)
105 {}
106 };
107
112};
113
115{
116 for (auto iter = faces.cbegin(); iter != faces.cend(); ++iter) {
117 iter.value()->cleanup();
118 if (!iter.value()->ref.deref())
119 delete iter.value();
120 }
121 faces.clear();
122
123 for (auto iter = staleFaces.cbegin(); iter != staleFaces.cend(); ++iter) {
124 (*iter)->cleanup();
125 if (!(*iter)->ref.deref())
126 delete *iter;
127 }
128 staleFaces.clear();
129
130 FT_Done_FreeType(library);
131 library = nullptr;
132}
133
134inline bool operator==(const QtFreetypeData::FaceStyle &style1, const QtFreetypeData::FaceStyle &style2)
135{
136 return style1.faceFileName == style2.faceFileName && style1.styleName == style2.styleName;
137}
138
139inline size_t qHash(const QtFreetypeData::FaceStyle &style, size_t seed)
140{
141 return qHashMulti(seed, style.faceFileName, style.styleName);
142}
143
144Q_GLOBAL_STATIC(QThreadStorage<QtFreetypeData *>, theFreetypeData)
145
147{
148 QtFreetypeData *&freetypeData = theFreetypeData()->localData();
149 if (!freetypeData)
150 freetypeData = new QtFreetypeData;
151 if (!freetypeData->library) {
152 FT_Init_FreeType(&freetypeData->library);
153#if defined(FT_FONT_FORMATS_H)
154 // Freetype defaults to disabling stem-darkening on CFF, we re-enable it.
155 FT_Bool no_darkening = false;
156 FT_Property_Set(freetypeData->library, "cff", "no-stem-darkening", &no_darkening);
157#endif
158 }
159 return freetypeData;
160}
161
163{
164 QtFreetypeData *freetypeData = qt_getFreetypeData();
165 Q_ASSERT(freetypeData->library);
166 return freetypeData->library;
167}
168
169int QFreetypeFace::fsType() const
170{
171 int fsType = 0;
172 TT_OS2 *os2 = (TT_OS2 *)FT_Get_Sfnt_Table(face, ft_sfnt_os2);
173 if (os2)
174 fsType = os2->fsType;
175 return fsType;
176}
177
178int QFreetypeFace::getPointInOutline(glyph_t glyph, int flags, quint32 point, QFixed *xpos, QFixed *ypos, quint32 *nPoints)
179{
180 if (int error = FT_Load_Glyph(face, glyph, flags))
181 return error;
182
183 if (face->glyph->format != FT_GLYPH_FORMAT_OUTLINE)
184 return Err_Invalid_SubTable;
185
186 *nPoints = face->glyph->outline.n_points;
187 if (!(*nPoints))
188 return Err_Ok;
189
190 if (point > *nPoints)
191 return Err_Invalid_SubTable;
192
193 *xpos = QFixed::fromFixed(face->glyph->outline.points[point].x);
194 *ypos = QFixed::fromFixed(face->glyph->outline.points[point].y);
195
196 return Err_Ok;
197}
198
199bool QFreetypeFace::isScalableBitmap() const
200{
201#ifdef FT_HAS_COLOR
202 return !FT_IS_SCALABLE(face) && FT_HAS_COLOR(face);
203#else
204 return false;
205#endif
206}
207
209
210/*
211 * One font file can contain more than one font (bold/italic for example)
212 * find the right one and return it.
213 *
214 * Returns the freetype face or 0 in case of an empty file or any other problems
215 * (like not being able to open the file)
216 */
217QFreetypeFace *QFreetypeFace::getFace(const QFontEngine::FaceId &face_id,
218 const QByteArray &fontData)
219{
220 if (face_id.filename.isEmpty() && fontData.isEmpty())
221 return nullptr;
222
223 QtFreetypeData *freetypeData = qt_getFreetypeData();
224
225 // Purge any stale face that is now ready to be deleted
226 freetypeData->staleFaces.removeIf([](QFreetypeFace *face) {
227 if (face->ref.loadRelaxed() == 1) {
228 face->cleanup();
229 delete face;
230 return true;
231 }
232 return false;
233 });
234
235 QFreetypeFace *freetype = nullptr;
236 auto it = freetypeData->faces.find(face_id);
237 if (it != freetypeData->faces.end()) {
238 freetype = *it;
239
240 Q_ASSERT(freetype->ref.loadRelaxed() > 0);
241 if (freetype->ref.loadRelaxed() == 1) {
242 // If there is only one reference left to the face, it means it is only referenced by
243 // the cache itself, and thus it is in cleanup state (but the final outside reference
244 // was removed on a different thread so it could not be deleted right away). We then
245 // complete the cleanup and pretend we didn't find it, so that it can be re-created with
246 // the present state.
247 freetype->cleanup();
248 freetypeData->faces.erase(it);
249 delete freetype;
250 freetype = nullptr;
251 } else {
252 freetype->ref.ref();
253 }
254 }
255
256 if (!freetype) {
257 const auto deleter = [](QFreetypeFace *f) { delete f; };
258 std::unique_ptr<QFreetypeFace, decltype(deleter)> newFreetype(new QFreetypeFace, deleter);
259 FT_Face face;
260 FT_Face tmpFace;
261 if (!face_id.filename.isEmpty()) {
262 QString fileName = QFile::decodeName(face_id.filename);
263 if (const char *prefix = ":qmemoryfonts/"; face_id.filename.startsWith(prefix)) {
264 // from qfontdatabase.cpp
265 QByteArray idx = face_id.filename;
266 idx.remove(0, strlen(prefix)); // remove ':qmemoryfonts/'
267 bool ok = false;
268 newFreetype->fontData = qt_fontdata_from_index(idx.toInt(&ok));
269 if (!ok)
270 newFreetype->fontData = QByteArray();
271 } else if (!QFileInfo(fileName).isNativePath()) {
272 QFile file(fileName);
273 if (!file.open(QIODevice::ReadOnly)) {
274 return nullptr;
275 }
276 newFreetype->fontData = file.readAll();
277 }
278 } else {
279 newFreetype->fontData = fontData;
280 }
281
282 FT_Int major, minor, patch;
283 FT_Library_Version(qt_getFreetype(), &major, &minor, &patch);
284 const bool goodVersion = major > 2 || (major == 2 && minor > 13) || (major == 2 && minor == 13 && patch > 2);
285
286 if (!newFreetype->fontData.isEmpty()) {
287 if (FT_New_Memory_Face(freetypeData->library,
288 (const FT_Byte *)newFreetype->fontData.constData(),
289 newFreetype->fontData.size(),
290 face_id.index,
291 &face)) {
292 return nullptr;
293 }
294
295 // On older Freetype versions, we create a temporary duplicate of the FT_Face to work
296 // around a bug, see further down.
297 if (goodVersion) {
298 tmpFace = face;
299 if (FT_Reference_Face(face))
300 tmpFace = nullptr;
301 } else if (!FT_HAS_MULTIPLE_MASTERS(face)
302 || FT_New_Memory_Face(freetypeData->library,
303 (const FT_Byte *)newFreetype->fontData.constData(),
304 newFreetype->fontData.size(),
305 face_id.index,
306 &tmpFace) != FT_Err_Ok) {
307 tmpFace = nullptr;
308 }
309 } else {
310 if (FT_New_Face(freetypeData->library, face_id.filename, face_id.index, &face))
311 return nullptr;
312
313 // On older Freetype versions, we create a temporary duplicate of the FT_Face to work
314 // around a bug, see further down.
315 if (goodVersion) {
316 tmpFace = face;
317 if (FT_Reference_Face(face))
318 tmpFace = nullptr;
319 } else if (!FT_HAS_MULTIPLE_MASTERS(face)
320 || FT_New_Face(freetypeData->library, face_id.filename, face_id.index, &tmpFace) != FT_Err_Ok) {
321 tmpFace = nullptr;
322 }
323 }
324
325#if (FREETYPE_MAJOR*10000 + FREETYPE_MINOR*100 + FREETYPE_PATCH) >= 20900
326 if (face_id.instanceIndex >= 0) {
327 qCDebug(lcFontMatch)
328 << "Selecting named instance" << (face_id.instanceIndex)
329 << "in" << face_id.filename;
330 FT_Set_Named_Instance(face, face_id.instanceIndex + 1);
331 }
332#endif
333
334 // Due to a bug in Freetype 2.13.2 and earlier causing just a call to FT_Get_MM_Var() on
335 // specific fonts to corrupt the FT_Face so that loading glyphs will later fail, we use a
336 // temporary FT_Face here which can be thrown away after. The bug has been fixed in
337 // Freetype 2.13.3.
338 if (tmpFace != nullptr) {
339 FT_MM_Var *var;
340 if (FT_Get_MM_Var(tmpFace, &var) == FT_Err_Ok) {
341 for (FT_UInt i = 0; i < var->num_axis; ++i) {
342 FT_Var_Axis *axis = var->axis + i;
343
344 QFontVariableAxis fontVariableAxis;
345 if (const auto tag = QFont::Tag::fromValue(axis->tag)) {
346 fontVariableAxis.setTag(*tag);
347 } else {
348 qWarning() << "QFreetypeFace::getFace: Invalid variable axis tag encountered"
349 << axis->tag;
350 }
351
352 fontVariableAxis.setMinimumValue(axis->minimum / 65536.0);
353 fontVariableAxis.setMaximumValue(axis->maximum / 65536.0);
354 fontVariableAxis.setDefaultValue(axis->def / 65536.0);
355 fontVariableAxis.setName(QString::fromUtf8(axis->name));
356
357 newFreetype->variableAxisList.append(fontVariableAxis);
358 }
359
360 if (!face_id.variableAxes.isEmpty()) {
361 QVarLengthArray<FT_Fixed, 16> coords(var->num_axis);
362 FT_Get_Var_Design_Coordinates(face, var->num_axis, coords.data());
363 for (qsizetype i = 0; i < newFreetype->variableAxisList.size(); ++i) {
364 const QFontVariableAxis &axis = newFreetype->variableAxisList.at(i);
365 if (axis.tag().isValid()) {
366 const auto it = face_id.variableAxes.constFind(axis.tag());
367 if (it != face_id.variableAxes.constEnd())
368 coords[i] = FT_Fixed(*it * 65536);
369 }
370 }
371 FT_Set_Var_Design_Coordinates(face, var->num_axis, coords.data());
372 }
373
374 FT_Done_MM_Var(qt_getFreetype(), var);
375 }
376
377 FT_Done_Face(tmpFace);
378 }
379
380 newFreetype->face = face;
381 newFreetype->mm_var = nullptr;
382 if (FT_IS_NAMED_INSTANCE(newFreetype->face)) {
383 FT_Error ftresult;
384 ftresult = FT_Get_MM_Var(face, &newFreetype->mm_var);
385 if (ftresult != FT_Err_Ok)
386 newFreetype->mm_var = nullptr;
387 }
388
389 newFreetype->ref.storeRelaxed(1);
390 newFreetype->xsize = 0;
391 newFreetype->ysize = 0;
392 newFreetype->matrix.xx = 0x10000;
393 newFreetype->matrix.yy = 0x10000;
394 newFreetype->matrix.xy = 0;
395 newFreetype->matrix.yx = 0;
396 newFreetype->unicode_map = nullptr;
397 newFreetype->symbol_map = nullptr;
398
399 memset(newFreetype->cmapCache, 0, sizeof(newFreetype->cmapCache));
400
401 for (int i = 0; i < newFreetype->face->num_charmaps; ++i) {
402 FT_CharMap cm = newFreetype->face->charmaps[i];
403 switch(cm->encoding) {
404 case FT_ENCODING_UNICODE:
405 newFreetype->unicode_map = cm;
406 break;
407 case FT_ENCODING_APPLE_ROMAN:
408 case FT_ENCODING_ADOBE_LATIN_1:
409 if (!newFreetype->unicode_map || newFreetype->unicode_map->encoding != FT_ENCODING_UNICODE)
410 newFreetype->unicode_map = cm;
411 break;
412 case FT_ENCODING_ADOBE_CUSTOM:
413 case FT_ENCODING_MS_SYMBOL:
414 if (!newFreetype->symbol_map)
415 newFreetype->symbol_map = cm;
416 break;
417 default:
418 break;
419 }
420 }
421
422 if (!FT_IS_SCALABLE(newFreetype->face) && newFreetype->face->num_fixed_sizes == 1)
423 FT_Set_Char_Size(face, newFreetype->face->available_sizes[0].x_ppem, newFreetype->face->available_sizes[0].y_ppem, 0, 0);
424
425 FT_Set_Charmap(newFreetype->face, newFreetype->unicode_map);
426
427 QT_TRY {
428 freetypeData->faces.insert(face_id, newFreetype.get());
429 } QT_CATCH(...) {
430 newFreetype.release()->release(face_id);
431 // we could return null in principle instead of throwing
432 QT_RETHROW;
433 }
434 freetype = newFreetype.release();
435 freetype->ref.ref();
436 }
437 return freetype;
438}
439
440void QFreetypeFace::cleanup()
441{
442 hbFace.reset();
443 if (mm_var)
444 FT_Done_MM_Var(qt_getFreetype(), mm_var);
445 mm_var = nullptr;
446 FT_Done_Face(face);
447 face = nullptr;
448}
449
450void QFreetypeFace::release(const QFontEngine::FaceId &face_id)
451{
452 Q_UNUSED(face_id);
453 bool deleteThis = !ref.deref();
454
455 // If the only reference left over is the cache's reference, we remove it from the cache,
456 // granted that we are on the correct thread. If not, we leave it there to be cleaned out
457 // later. While we are at it, we also purge all left over faces which are only referenced
458 // from the cache.
459 if (face && ref.loadRelaxed() == 1) {
460 QtFreetypeData *freetypeData = qt_getFreetypeData();
461
462 freetypeData->staleFaces.removeIf([&deleteThis, this](QFreetypeFace *face){
463 if (face->ref.loadRelaxed() == 1) {
464 face->cleanup();
465 if (face == this)
466 deleteThis = true;
467 else
468 delete face;
469 return true;
470 }
471 return false;
472 });
473
474 for (auto it = freetypeData->faces.constBegin();
475 it != freetypeData->faces.constEnd();
476 it = freetypeData->faces.erase(it)) {
477 if (it.value()->ref.loadRelaxed() == 1) {
478 it.value()->cleanup();
479 if (it.value() == this)
480 deleteThis = true; // This face, delete at end of function for safety
481 else
482 delete it.value();
483 } else {
484 freetypeData->staleFaces.append(it.value());
485 }
486 }
487
488 if (freetypeData->faces.isEmpty() && freetypeData->staleFaces.isEmpty()) {
489 FT_Done_FreeType(freetypeData->library);
490 freetypeData->library = nullptr;
491 }
492 }
493
494 if (deleteThis)
495 delete this;
496}
497
498static int computeFaceIndex(const QString &faceFileName, const QString &styleName)
499{
500 FT_Library library = qt_getFreetype();
501
502 int faceIndex = 0;
503 int numFaces = 0;
504
505 do {
506 FT_Face face;
507
508 FT_Error error = FT_New_Face(library, faceFileName.toUtf8().constData(), faceIndex, &face);
509 if (error != FT_Err_Ok) {
510 qDebug() << "FT_New_Face failed for face index" << faceIndex << ':' << Qt::hex << error;
511 break;
512 }
513
514 const bool found = QLatin1StringView(face->style_name) == styleName;
515 numFaces = face->num_faces;
516
517 FT_Done_Face(face);
518
519 if (found)
520 return faceIndex;
521 } while (++faceIndex < numFaces);
522
523 // Fall back to the first font face in the file
524 return 0;
525}
526
527int QFreetypeFace::getFaceIndexByStyleName(const QString &faceFileName, const QString &styleName)
528{
529 QtFreetypeData *freetypeData = qt_getFreetypeData();
530
531 // Try to get from cache
532 QtFreetypeData::FaceStyle faceStyle(faceFileName, styleName);
533 int faceIndex = freetypeData->faceIndices.value(faceStyle, -1);
534
535 if (faceIndex >= 0)
536 return faceIndex;
537
538 faceIndex = computeFaceIndex(faceFileName, styleName);
539
540 freetypeData->faceIndices.insert(faceStyle, faceIndex);
541
542 return faceIndex;
543}
544
545void QFreetypeFace::computeSize(const QFontDef &fontDef, int *xsize, int *ysize, bool *outline_drawing, QFixed *scalableBitmapScaleFactor)
546{
547 *ysize = qRound(fontDef.pixelSize * 64);
548 *xsize = *ysize * fontDef.stretch / 100;
549 *scalableBitmapScaleFactor = 1;
550 *outline_drawing = false;
551
552 if (!(face->face_flags & FT_FACE_FLAG_SCALABLE)) {
553 int best = 0;
554 if (!isScalableBitmap()) {
555 /*
556 * Bitmap only faces must match exactly, so find the closest
557 * one (height dominant search)
558 */
559 for (int i = 1; i < face->num_fixed_sizes; i++) {
560 if (qAbs(*ysize - face->available_sizes[i].y_ppem) <
561 qAbs(*ysize - face->available_sizes[best].y_ppem) ||
562 (qAbs(*ysize - face->available_sizes[i].y_ppem) ==
563 qAbs(*ysize - face->available_sizes[best].y_ppem) &&
564 qAbs(*xsize - face->available_sizes[i].x_ppem) <
565 qAbs(*xsize - face->available_sizes[best].x_ppem))) {
566 best = i;
567 }
568 }
569 } else {
570 // Select the shortest bitmap strike whose height is larger than the desired height
571 for (int i = 1; i < face->num_fixed_sizes; i++) {
572 if (face->available_sizes[i].y_ppem < *ysize) {
573 if (face->available_sizes[i].y_ppem > face->available_sizes[best].y_ppem)
574 best = i;
575 } else if (face->available_sizes[best].y_ppem < *ysize) {
576 best = i;
577 } else if (face->available_sizes[i].y_ppem < face->available_sizes[best].y_ppem) {
578 best = i;
579 }
580 }
581 }
582
583 // According to freetype documentation we must use FT_Select_Size
584 // to make sure we can select the desired bitmap strike index
585 if (FT_Select_Size(face, best) == 0) {
586 if (isScalableBitmap())
587 *scalableBitmapScaleFactor = QFixed::fromReal((qreal)fontDef.pixelSize / face->available_sizes[best].height);
588 *xsize = face->available_sizes[best].x_ppem;
589 *ysize = face->available_sizes[best].y_ppem;
590 } else {
591 *xsize = *ysize = 0;
592 }
593 } else {
594#if defined FT_HAS_COLOR
595 if (FT_HAS_COLOR(face)) {
596 *outline_drawing = false;
597 } else
598#endif
599 {
600 int maxCachedGlyphSize = QFontEngine::maxCachedGlyphSize();
601 *outline_drawing = (*xsize > (maxCachedGlyphSize << 6) || *ysize > (maxCachedGlyphSize << 6));
602 }
603 }
604}
605
606QFontEngine::Properties QFreetypeFace::properties() const
607{
608 QFontEngine::Properties p;
609 p.postscriptName = FT_Get_Postscript_Name(face);
610 PS_FontInfoRec font_info;
611 if (FT_Get_PS_Font_Info(face, &font_info) == 0)
612 p.copyright = font_info.notice;
613 if (FT_IS_SCALABLE(face)
614#if defined(FT_HAS_COLOR)
615 && !FT_HAS_COLOR(face)
616#endif
617 ) {
618 p.ascent = face->ascender;
619 p.descent = -face->descender;
620 p.leading = face->height - face->ascender + face->descender;
621 p.emSquare = face->units_per_EM;
622 p.boundingBox = QRectF(face->bbox.xMin, -face->bbox.yMax,
623 face->bbox.xMax - face->bbox.xMin,
624 face->bbox.yMax - face->bbox.yMin);
625 } else {
626 p.ascent = QFixed::fromFixed(face->size->metrics.ascender);
627 p.descent = QFixed::fromFixed(-face->size->metrics.descender);
628 p.leading = QFixed::fromFixed(face->size->metrics.height - face->size->metrics.ascender + face->size->metrics.descender);
629 p.emSquare = face->size->metrics.y_ppem;
630// p.boundingBox = QRectF(-p.ascent.toReal(), 0, (p.ascent + p.descent).toReal(), face->size->metrics.max_advance/64.);
631 p.boundingBox = QRectF(0, -p.ascent.toReal(),
632 face->size->metrics.max_advance/64, (p.ascent + p.descent).toReal() );
633 }
634 p.italicAngle = 0;
635 p.capHeight = p.ascent;
636 p.lineWidth = face->underline_thickness;
637
638 return p;
639}
640
641bool QFreetypeFace::getSfntTable(uint tag, uchar *buffer, uint *length) const
642{
643 return ft_getSfntTable(face, tag, buffer, length);
644}
645
646/* Some fonts (such as MingLiu rely on hinting to scale different
647 components to their correct sizes. While this is really broken (it
648 should be done in the component glyph itself, not the hinter) we
649 will have to live with it.
650
651 This means we can not use FT_LOAD_NO_HINTING to get the glyph
652 outline. All we can do is to load the unscaled glyph and scale it
653 down manually when required.
654*/
655static void scaleOutline(FT_Face face, FT_GlyphSlot g, FT_Fixed x_scale, FT_Fixed y_scale)
656{
657 x_scale = FT_MulDiv(x_scale, 1 << 10, face->units_per_EM);
658 y_scale = FT_MulDiv(y_scale, 1 << 10, face->units_per_EM);
659 FT_Vector *p = g->outline.points;
660 const FT_Vector *e = p + g->outline.n_points;
661 while (p < e) {
662 p->x = FT_MulFix(p->x, x_scale);
663 p->y = FT_MulFix(p->y, y_scale);
664 ++p;
665 }
666}
667
668#define GLYPH2PATH_DEBUG QT_NO_QDEBUG_MACRO // qDebug
669void QFreetypeFace::addGlyphToPath(FT_Face face, FT_GlyphSlot g, const QFixedPoint &point, QPainterPath *path, FT_Fixed x_scale, FT_Fixed y_scale)
670{
671 const qreal factor = 1/64.;
672 scaleOutline(face, g, x_scale, y_scale);
673
674 QPointF cp = point.toPointF();
675
676 // convert the outline to a painter path
677 int i = 0;
678 for (int j = 0; j < g->outline.n_contours; ++j) {
679 int last_point = g->outline.contours[j];
680 GLYPH2PATH_DEBUG() << "contour:" << i << "to" << last_point;
681 QPointF start = QPointF(g->outline.points[i].x*factor, -g->outline.points[i].y*factor);
682 if (!(g->outline.tags[i] & 1)) { // start point is not on curve:
683 if (!(g->outline.tags[last_point] & 1)) { // end point is not on curve:
684 GLYPH2PATH_DEBUG() << " start and end point are not on curve";
685 start = (QPointF(g->outline.points[last_point].x*factor,
686 -g->outline.points[last_point].y*factor) + start) / 2.0;
687 } else {
688 GLYPH2PATH_DEBUG() << " end point is on curve, start is not";
689 start = QPointF(g->outline.points[last_point].x*factor,
690 -g->outline.points[last_point].y*factor);
691 }
692 --i; // to use original start point as control point below
693 }
694 start += cp;
695 GLYPH2PATH_DEBUG() << " start at" << start;
696
697 path->moveTo(start);
698 QPointF c[4];
699 c[0] = start;
700 int n = 1;
701 while (i < last_point) {
702 ++i;
703 c[n] = cp + QPointF(g->outline.points[i].x*factor, -g->outline.points[i].y*factor);
704 GLYPH2PATH_DEBUG() << " " << i << c[n] << "tag =" << (int)g->outline.tags[i]
705 << ": on curve =" << (bool)(g->outline.tags[i] & 1);
706 ++n;
707 switch (g->outline.tags[i] & 3) {
708 case 2:
709 // cubic bezier element
710 if (n < 4)
711 continue;
712 c[3] = (c[3] + c[2])/2;
713 --i;
714 break;
715 case 0:
716 // quadratic bezier element
717 if (n < 3)
718 continue;
719 c[3] = (c[1] + c[2])/2;
720 c[2] = (2*c[1] + c[3])/3;
721 c[1] = (2*c[1] + c[0])/3;
722 --i;
723 break;
724 case 1:
725 case 3:
726 if (n == 2) {
727 GLYPH2PATH_DEBUG() << " lineTo" << c[1];
728 path->lineTo(c[1]);
729 c[0] = c[1];
730 n = 1;
731 continue;
732 } else if (n == 3) {
733 c[3] = c[2];
734 c[2] = (2*c[1] + c[3])/3;
735 c[1] = (2*c[1] + c[0])/3;
736 }
737 break;
738 }
739 GLYPH2PATH_DEBUG() << " cubicTo" << c[1] << c[2] << c[3];
740 path->cubicTo(c[1], c[2], c[3]);
741 c[0] = c[3];
742 n = 1;
743 }
744
745 if (n == 1) {
746 GLYPH2PATH_DEBUG() << " closeSubpath";
747 path->closeSubpath();
748 } else {
749 c[3] = start;
750 if (n == 2) {
751 c[2] = (2*c[1] + c[3])/3;
752 c[1] = (2*c[1] + c[0])/3;
753 }
754 GLYPH2PATH_DEBUG() << " close cubicTo" << c[1] << c[2] << c[3];
755 path->cubicTo(c[1], c[2], c[3]);
756 }
757 ++i;
758 }
759}
760
761extern void qt_addBitmapToPath(qreal x0, qreal y0, const uchar *image_data, int bpl, int w, int h, QPainterPath *path);
762
763void QFreetypeFace::addBitmapToPath(FT_GlyphSlot slot, const QFixedPoint &point, QPainterPath *path)
764{
765 if (slot->format != FT_GLYPH_FORMAT_BITMAP
766 || slot->bitmap.pixel_mode != FT_PIXEL_MODE_MONO)
767 return;
768
769 QPointF cp = point.toPointF();
770 qt_addBitmapToPath(cp.x() + TRUNC(slot->metrics.horiBearingX), cp.y() - TRUNC(slot->metrics.horiBearingY),
771 slot->bitmap.buffer, slot->bitmap.pitch, slot->bitmap.width, slot->bitmap.rows, path);
772}
773
774static inline void convertRGBToARGB(const uchar *src, uint *dst, int width, int height, int src_pitch, bool bgr)
775{
776 const int offs = bgr ? -1 : 1;
777 const int w = width * 3;
778 while (height--) {
779 uint *dd = dst;
780 for (int x = 0; x < w; x += 3) {
781 uchar red = src[x + 1 - offs];
782 uchar green = src[x + 1];
783 uchar blue = src[x + 1 + offs];
784 *dd++ = (0xFFU << 24) | (red << 16) | (green << 8) | blue;
785 }
786 dst += width;
787 src += src_pitch;
788 }
789}
790
791static inline void convertRGBToARGB_V(const uchar *src, uint *dst, int width, int height, int src_pitch, bool bgr)
792{
793 const int offs = bgr ? -src_pitch : src_pitch;
794 while (height--) {
795 for (int x = 0; x < width; x++) {
796 uchar red = src[x + src_pitch - offs];
797 uchar green = src[x + src_pitch];
798 uchar blue = src[x + src_pitch + offs];
799 *dst++ = (0XFFU << 24) | (red << 16) | (green << 8) | blue;
800 }
801 src += 3*src_pitch;
802 }
803}
804
806{
807 static int type = -1;
808 if (type == -1) {
809 if (QScreen *screen = QGuiApplication::primaryScreen())
810 type = screen->handle()->subpixelAntialiasingTypeHint();
811 }
812 return static_cast<QFontEngine::SubpixelAntialiasingType>(type);
813}
814
815QFontEngineFT *QFontEngineFT::create(const QFontDef &fontDef, FaceId faceId, const QByteArray &fontData)
816{
817 auto engine = std::make_unique<QFontEngineFT>(fontDef);
818
819 QFontEngineFT::GlyphFormat format = QFontEngineFT::Format_Mono;
820 const bool antialias = !(fontDef.styleStrategy & QFont::NoAntialias);
821
822 if (antialias) {
823 QFontEngine::SubpixelAntialiasingType subpixelType = subpixelAntialiasingTypeHint();
824 if (subpixelType == QFontEngine::Subpixel_None || (fontDef.styleStrategy & QFont::NoSubpixelAntialias)) {
825 format = QFontEngineFT::Format_A8;
826 engine->subpixelType = QFontEngine::Subpixel_None;
827 } else {
828 format = QFontEngineFT::Format_A32;
829 engine->subpixelType = subpixelType;
830 }
831 }
832
833 if (!engine->init(faceId, antialias, format, fontData) || engine->invalid()) {
834 qWarning("QFontEngineFT: Failed to create FreeType font engine");
835 return nullptr;
836 }
837
838 engine->setQtDefaultHintStyle(static_cast<QFont::HintingPreference>(fontDef.hintingPreference));
839 return engine.release();
840}
841
842static FT_UShort calculateActualWeight(QFreetypeFace *freetypeFace, FT_Face face, QFontEngine::FaceId faceId)
843{
844 FT_MM_Var *var = freetypeFace->mm_var;
845 if (var != nullptr && faceId.instanceIndex >= 0 && FT_UInt(faceId.instanceIndex) < var->num_namedstyles) {
846 for (FT_UInt axis = 0; axis < var->num_axis; ++axis) {
847 if (var->axis[axis].tag == QFont::Tag("wght").value()) {
848 return var->namedstyle[faceId.instanceIndex].coords[axis] >> 16;
849 }
850 }
851 }
852 if (const TT_OS2 *os2 = reinterpret_cast<const TT_OS2 *>(FT_Get_Sfnt_Table(face, ft_sfnt_os2))) {
853 return os2->usWeightClass;
854 }
855
856 return 700;
857}
858
859namespace {
860 class QFontEngineFTRawData: public QFontEngineFT
861 {
862 public:
863 QFontEngineFTRawData(const QFontDef &fontDef) : QFontEngineFT(fontDef)
864 {
865 }
866
867 void updateFamilyNameAndStyle()
868 {
869 fontDef.families = QStringList(QString::fromLatin1(freetype->face->family_name));
870
871 if (freetype->face->style_flags & FT_STYLE_FLAG_ITALIC)
872 fontDef.style = QFont::StyleItalic;
873
874 if (freetype->face->style_flags & FT_STYLE_FLAG_BOLD)
875 fontDef.weight = QFont::Bold;
876 else
877 fontDef.weight = calculateActualWeight(freetype, freetype->face, faceId());
878 }
879
880 bool initFromData(const QByteArray &fontData,
881 const QMap<QFont::Tag, float> &variableAxisValues,
882 int instanceIndex)
883 {
884 FaceId faceId;
885 faceId.filename = "";
886 faceId.index = 0;
887 faceId.uuid = QUuid::createUuid().toByteArray();
888 faceId.variableAxes = variableAxisValues;
889 faceId.instanceIndex = instanceIndex;
890
891 return init(faceId, true, Format_None, fontData);
892 }
893 };
894}
895
896QFontEngineFT *QFontEngineFT::create(const QByteArray &fontData,
897 qreal pixelSize,
898 QFont::HintingPreference hintingPreference,
899 const QMap<QFont::Tag, float> &variableAxisValues,
900 int instanceIndex)
901{
902 QFontDef fontDef;
903 fontDef.pixelSize = pixelSize;
904 fontDef.stretch = QFont::Unstretched;
905 fontDef.hintingPreference = hintingPreference;
906 fontDef.variableAxisValues = variableAxisValues;
907
908 QFontEngineFTRawData *fe = new QFontEngineFTRawData(fontDef);
909 if (!fe->initFromData(fontData, variableAxisValues, instanceIndex)) {
910 delete fe;
911 return nullptr;
912 }
913
914 fe->updateFamilyNameAndStyle();
915 fe->setQtDefaultHintStyle(static_cast<QFont::HintingPreference>(fontDef.hintingPreference));
916
917 return fe;
918}
919
920QFontEngineFT::QFontEngineFT(const QFontDef &fd)
921 : QFontEngine(Freetype)
922{
923 fontDef = fd;
924 matrix.xx = 0x10000;
925 matrix.yy = 0x10000;
926 matrix.xy = 0;
927 matrix.yx = 0;
928 cache_cost = 100 * 1024;
929 kerning_pairs_loaded = false;
930 transform = false;
931 embolden = false;
932 obliquen = false;
933 antialias = true;
934 freetype = nullptr;
935 default_load_flags = FT_LOAD_IGNORE_GLOBAL_ADVANCE_WIDTH;
936 default_hint_style = ftInitialDefaultHintStyle;
937 subpixelType = Subpixel_None;
938 lcdFilterType = (int)((quintptr) FT_LCD_FILTER_DEFAULT);
939 defaultFormat = Format_None;
940 embeddedbitmap = false;
941 const QByteArray env = qgetenv("QT_NO_FT_CACHE");
942 cacheEnabled = env.isEmpty() || env.toInt() == 0;
943 m_subPixelPositionCount = 4;
944 forceAutoHint = false;
945 stemDarkeningDriver = false;
946 verticalHintingOnly = false;
947}
948
949QFontEngineFT::~QFontEngineFT()
950{
951 if (freetype)
952 freetype->release(face_id);
953}
954
955bool QFontEngineFT::init(FaceId faceId, bool antialias, GlyphFormat format,
956 const QByteArray &fontData)
957{
958 return init(faceId, antialias, format, QFreetypeFace::getFace(faceId, fontData));
959}
960
961static void dont_delete(void*) {}
962
963static bool calculateActualItalic(QFreetypeFace *freetypeFace, FT_Face face, QFontEngine::FaceId faceId)
964{
965 FT_MM_Var *var = freetypeFace->mm_var;
966 if (var != nullptr && faceId.instanceIndex >= 0 && FT_UInt(faceId.instanceIndex) < var->num_namedstyles) {
967 for (FT_UInt axis = 0; axis < var->num_axis; ++axis) {
968 if (var->axis[axis].tag == QFont::Tag("ital").value()) {
969 return (var->namedstyle[faceId.instanceIndex].coords[axis] >> 16) == 1;
970 }
971 }
972 }
973
974 return (face->style_flags & FT_STYLE_FLAG_ITALIC);
975}
976
977bool QFontEngineFT::init(FaceId faceId, bool antialias, GlyphFormat format,
978 QFreetypeFace *freetypeFace)
979{
980 freetype = freetypeFace;
981 if (!freetype) {
982 xsize = 0;
983 ysize = 0;
984 return false;
985 }
986 defaultFormat = format;
987 this->antialias = antialias;
988
989 if (!antialias)
990 glyphFormat = QFontEngine::Format_Mono;
991 else
992 glyphFormat = defaultFormat;
993
994 face_id = faceId;
995
996 symbol = freetype->symbol_map != nullptr;
997 PS_FontInfoRec psrec;
998 // don't assume that type1 fonts are symbol fonts by default
999 if (FT_Get_PS_Font_Info(freetype->face, &psrec) == FT_Err_Ok) {
1000 symbol = !fontDef.families.isEmpty() && bool(fontDef.families.constFirst().contains("symbol"_L1, Qt::CaseInsensitive));
1001 }
1002
1003 freetype->computeSize(fontDef, &xsize, &ysize, &defaultGlyphSet.outline_drawing, &scalableBitmapScaleFactor);
1004
1005 FT_Face face = lockFace();
1006
1007 if (FT_IS_SCALABLE(face)
1008#if defined(FT_HAS_COLOR)
1009 && !FT_HAS_COLOR(face)
1010#endif
1011 ) {
1012 bool isItalic = calculateActualItalic(freetype, face, faceId);
1013 bool fake_oblique = (fontDef.style != QFont::StyleNormal) && !isItalic && !qEnvironmentVariableIsSet("QT_NO_SYNTHESIZED_ITALIC");
1014 if (fake_oblique)
1015 obliquen = true;
1016 FT_Set_Transform(face, &matrix, nullptr);
1017 freetype->matrix = matrix;
1018 // fake bold
1019 if ((fontDef.weight >= QFont::Bold) && !(face->style_flags & FT_STYLE_FLAG_BOLD) && !FT_IS_FIXED_WIDTH(face) && !qEnvironmentVariableIsSet("QT_NO_SYNTHESIZED_BOLD")) {
1020 FT_UShort actualWeight = calculateActualWeight(freetype, face, faceId);
1021 if (actualWeight < 700 &&
1022 (fontDef.pixelSize < 64 || qEnvironmentVariableIsSet("QT_NO_SYNTHESIZED_BOLD_LIMIT"))) {
1023 embolden = true;
1024 }
1025 }
1026 // underline metrics
1027 line_thickness = QFixed::fromFixed(FT_MulFix(face->underline_thickness, face->size->metrics.y_scale));
1028 QFixed center_position = QFixed::fromFixed(-FT_MulFix(face->underline_position, face->size->metrics.y_scale));
1029 underline_position = center_position - line_thickness / 2;
1030 } else {
1031 // ad hoc algorithm
1032 int score = fontDef.weight * fontDef.pixelSize;
1033 line_thickness = score / 7000;
1034 // looks better with thicker line for small pointsizes
1035 if (line_thickness < 2 && score >= 1050)
1036 line_thickness = 2;
1037 underline_position = ((line_thickness * 2) + 3) / 6;
1038
1039 cacheEnabled = false;
1040#if defined(FT_HAS_COLOR)
1041 if (FT_HAS_COLOR(face))
1042 glyphFormat = defaultFormat = GlyphFormat::Format_ARGB;
1043#endif
1044 }
1045 if (line_thickness < 1)
1046 line_thickness = 1;
1047
1048 metrics = face->size->metrics;
1049
1050 /*
1051 TrueType fonts with embedded bitmaps may have a bitmap font specific
1052 ascent/descent in the EBLC table. There is no direct public API
1053 to extract those values. The only way we've found is to trick freetype
1054 into thinking that it's not a scalable font in FT_Select_Size so that
1055 the metrics are retrieved from the bitmap strikes.
1056 */
1057 if (FT_IS_SCALABLE(face)) {
1058 for (int i = 0; i < face->num_fixed_sizes; ++i) {
1059 if (xsize == face->available_sizes[i].x_ppem && ysize == face->available_sizes[i].y_ppem) {
1060 face->face_flags &= ~FT_FACE_FLAG_SCALABLE;
1061
1062 FT_Select_Size(face, i);
1063 if (face->size->metrics.ascender + face->size->metrics.descender > 0) {
1064 FT_Pos leading = metrics.height - metrics.ascender + metrics.descender;
1065 metrics.ascender = face->size->metrics.ascender;
1066 metrics.descender = face->size->metrics.descender;
1067 if (metrics.descender > 0
1068 && QString::fromUtf8(face->family_name) == "Courier New"_L1) {
1069 metrics.descender *= -1;
1070 }
1071 metrics.height = metrics.ascender - metrics.descender + leading;
1072 }
1073 FT_Set_Char_Size(face, xsize, ysize, 0, 0);
1074
1075 face->face_flags |= FT_FACE_FLAG_SCALABLE;
1076 break;
1077 }
1078 }
1079 }
1080#if defined(FT_FONT_FORMATS_H)
1081 const char *fmt = FT_Get_Font_Format(face);
1082 if (fmt && qstrncmp(fmt, "CFF", 4) == 0) {
1083 FT_Bool no_stem_darkening = true;
1084 FT_Error err = FT_Property_Get(qt_getFreetype(), "cff", "no-stem-darkening", &no_stem_darkening);
1085 if (err == FT_Err_Ok)
1086 stemDarkeningDriver = !no_stem_darkening;
1087 else
1088 stemDarkeningDriver = false;
1089 }
1090#endif
1091
1092 fontDef.styleName = QString::fromUtf8(face->style_name);
1093
1094 if (!freetype->hbFace) {
1095 faceData.user_data = face;
1096 faceData.get_font_table = ft_getSfntTable;
1097 (void)harfbuzzFace(); // populates face_
1098 freetype->hbFace = std::move(face_);
1099 } else {
1100 Q_ASSERT(!face_);
1101 }
1102 // we share the HB face in QFreeTypeFace, so do not let ~QFontEngine() destroy it
1103 face_ = Holder(freetype->hbFace.get(), dont_delete);
1104
1105 unlockFace();
1106
1107 fsType = freetype->fsType();
1108 return true;
1109}
1110
1111void QFontEngineFT::setQtDefaultHintStyle(QFont::HintingPreference hintingPreference)
1112{
1113 switch (hintingPreference) {
1114 case QFont::PreferNoHinting:
1115 setDefaultHintStyle(HintNone);
1116 break;
1117 case QFont::PreferFullHinting:
1118 setDefaultHintStyle(HintFull);
1119 break;
1120 case QFont::PreferVerticalHinting:
1121 setDefaultHintStyle(HintLight);
1122 break;
1123 case QFont::PreferDefaultHinting:
1124 setDefaultHintStyle(ftInitialDefaultHintStyle);
1125 break;
1126 }
1127}
1128
1129void QFontEngineFT::setDefaultHintStyle(HintStyle style)
1130{
1131 default_hint_style = style;
1132}
1133
1134// Since version 40 the TrueType interpreter moves points horizontally only for monochrome glyphs.
1135static bool hasTrueTypeInterpreterV40(FT_Library library)
1136{
1137#if defined(FT_FONT_FORMATS_H)
1138 FT_UInt version = 0;
1139 if (FT_Property_Get(library, "truetype", "interpreter-version", &version) != FT_Err_Ok)
1140 return true; // No TrueType driver, so no interpreter either
1141 return version == 40;
1142#else
1143 Q_UNUSED(library);
1144 return false;
1145#endif
1146}
1147
1148// Returns false if the hinting may still fit glyphs to the pixel grid horizontally.
1149bool QFontEngineFT::setVerticalHintingOnly()
1150{
1151 // Monochrome glyphs get fitted both ways by any hinter
1152 if (!antialias)
1153 return false;
1154 if ((default_hint_style == HintMedium || default_hint_style == HintFull)
1155 && !hasTrueTypeInterpreterV40(qt_getFreetype())) {
1156 return false;
1157 }
1158 verticalHintingOnly = true;
1159 return true;
1160}
1161
1162bool QFontEngineFT::expectsGammaCorrectedBlending(QFontEngine::GlyphFormat format) const
1163{
1164 Q_UNUSED(format);
1165 return stemDarkeningDriver;
1166}
1167
1168int QFontEngineFT::loadFlags(QGlyphSet *set, GlyphFormat format, int flags,
1169 bool &hsubpixel, int &vfactor) const
1170{
1171 int load_flags = FT_LOAD_DEFAULT | default_load_flags;
1172 int load_target = default_hint_style == HintLight
1173 ? FT_LOAD_TARGET_LIGHT
1174 : verticalHintingOnly
1175 ? FT_LOAD_TARGET_LCD // Makes the auto-hinter work vertically only
1176 : FT_LOAD_TARGET_NORMAL;
1177
1178 if (format == Format_Mono) {
1179 load_target = FT_LOAD_TARGET_MONO;
1180 } else if (format == Format_A32) {
1181 if (subpixelType == Subpixel_RGB || subpixelType == Subpixel_BGR)
1182 hsubpixel = true;
1183 else if (subpixelType == Subpixel_VRGB || subpixelType == Subpixel_VBGR)
1184 vfactor = 3;
1185 } else if (format == Format_ARGB) {
1186#ifdef FT_LOAD_COLOR
1187 load_flags |= FT_LOAD_COLOR;
1188#endif
1189 }
1190
1191 if (set && set->outline_drawing)
1192 load_flags |= FT_LOAD_NO_BITMAP;
1193
1194 if (default_hint_style == HintNone || (flags & DesignMetrics) || (set && set->outline_drawing))
1195 load_flags |= FT_LOAD_NO_HINTING;
1196 else
1197 load_flags |= load_target;
1198
1199 if (forceAutoHint)
1200 load_flags |= FT_LOAD_FORCE_AUTOHINT;
1201
1202 return load_flags;
1203}
1204
1205static inline bool areMetricsTooLarge(const QFontEngineFT::GlyphInfo &info)
1206{
1207 // false if exceeds QFontEngineFT::Glyph metrics
1208 return info.width > 0xFF || info.height > 0xFF || info.linearAdvance > 0x7FFF;
1209}
1210
1211static inline void transformBoundingBox(int *left, int *top, int *right, int *bottom, FT_Matrix *matrix)
1212{
1213 int l, r, t, b;
1214 FT_Vector vector;
1215 vector.x = *left;
1216 vector.y = *top;
1217 FT_Vector_Transform(&vector, matrix);
1218 l = r = vector.x;
1219 t = b = vector.y;
1220 vector.x = *right;
1221 vector.y = *top;
1222 FT_Vector_Transform(&vector, matrix);
1223 if (l > vector.x) l = vector.x;
1224 if (r < vector.x) r = vector.x;
1225 if (t < vector.y) t = vector.y;
1226 if (b > vector.y) b = vector.y;
1227 vector.x = *right;
1228 vector.y = *bottom;
1229 FT_Vector_Transform(&vector, matrix);
1230 if (l > vector.x) l = vector.x;
1231 if (r < vector.x) r = vector.x;
1232 if (t < vector.y) t = vector.y;
1233 if (b > vector.y) b = vector.y;
1234 vector.x = *left;
1235 vector.y = *bottom;
1236 FT_Vector_Transform(&vector, matrix);
1237 if (l > vector.x) l = vector.x;
1238 if (r < vector.x) r = vector.x;
1239 if (t < vector.y) t = vector.y;
1240 if (b > vector.y) b = vector.y;
1241 *left = l;
1242 *right = r;
1243 *top = t;
1244 *bottom = b;
1245}
1246
1247#if defined(QFONTENGINE_FT_SUPPORT_COLRV1)
1248#define FROM_FIXED_16_16(value) (value / 65536.0)
1249
1250static inline QTransform FTAffineToQTransform(const FT_Affine23 &matrix)
1251{
1252 qreal m11 = FROM_FIXED_16_16(matrix.xx);
1253 qreal m21 = -FROM_FIXED_16_16(matrix.xy);
1254 qreal m12 = -FROM_FIXED_16_16(matrix.yx);
1255 qreal m22 = FROM_FIXED_16_16(matrix.yy);
1256 qreal dx = FROM_FIXED_16_16(matrix.dx);
1257 qreal dy = -FROM_FIXED_16_16(matrix.dy);
1258
1259 return QTransform(m11, m12, m21, m22, dx, dy);
1260}
1261
1262bool QFontEngineFT::traverseColr1(FT_OpaquePaint opaquePaint,
1263 QSet<std::pair<FT_Byte *, FT_Bool> > *loops,
1264 QColor foregroundColor,
1265 FT_Color *palette,
1266 ushort paletteCount,
1267 QColrPaintGraphRenderer *paintGraphRenderer) const
1268{
1269 FT_Face face = freetype->face;
1270
1271 auto key = std::pair{opaquePaint.p, opaquePaint.insert_root_transform};
1272 if (loops->contains(key)) {
1273 qCWarning(lcColrv1) << "Cycle detected in COLRv1 graph";
1274 return false;
1275 }
1276
1277 paintGraphRenderer->save();
1278 loops->insert(key);
1279 auto cleanup = qScopeGuard([&paintGraphRenderer, &key, &loops]() {
1280 loops->remove(key);
1281 paintGraphRenderer->restore();
1282 });
1283
1284 FT_COLR_Paint paint;
1285 if (!FT_Get_Paint(face, opaquePaint, &paint))
1286 return false;
1287
1288 if (paint.format == FT_COLR_PAINTFORMAT_COLR_LAYERS) {
1289 FT_OpaquePaint layerPaint;
1290 layerPaint.p = nullptr;
1291 while (FT_Get_Paint_Layers(face, &paint.u.colr_layers.layer_iterator, &layerPaint)) {
1292 if (!traverseColr1(layerPaint, loops, foregroundColor, palette, paletteCount, paintGraphRenderer))
1293 return false;
1294 }
1295 } else if (paint.format == FT_COLR_PAINTFORMAT_TRANSFORM
1296 || paint.format == FT_COLR_PAINTFORMAT_SCALE
1297 || paint.format == FT_COLR_PAINTFORMAT_TRANSLATE
1298 || paint.format == FT_COLR_PAINTFORMAT_ROTATE
1299 || paint.format == FT_COLR_PAINTFORMAT_SKEW) {
1300 QTransform xform;
1301
1302 FT_OpaquePaint nextPaint;
1303 switch (paint.format) {
1304 case FT_COLR_PAINTFORMAT_TRANSFORM:
1305 xform = FTAffineToQTransform(paint.u.transform.affine);
1306 nextPaint = paint.u.transform.paint;
1307 break;
1308 case FT_COLR_PAINTFORMAT_SCALE:
1309 {
1310 qreal centerX = FROM_FIXED_16_16(paint.u.scale.center_x);
1311 qreal centerY = -FROM_FIXED_16_16(paint.u.scale.center_y);
1312 qreal scaleX = FROM_FIXED_16_16(paint.u.scale.scale_x);
1313 qreal scaleY = FROM_FIXED_16_16(paint.u.scale.scale_y);
1314
1315 xform.translate(centerX, centerY);
1316 xform.scale(scaleX, scaleY);
1317 xform.translate(-centerX, -centerY);
1318
1319 nextPaint = paint.u.scale.paint;
1320 break;
1321 }
1322 case FT_COLR_PAINTFORMAT_ROTATE:
1323 {
1324 qreal centerX = FROM_FIXED_16_16(paint.u.rotate.center_x);
1325 qreal centerY = -FROM_FIXED_16_16(paint.u.rotate.center_y);
1326 qreal angle = -FROM_FIXED_16_16(paint.u.rotate.angle) * 180.0;
1327
1328 xform.translate(centerX, centerY);
1329 xform.rotate(angle);
1330 xform.translate(-centerX, -centerY);
1331
1332 nextPaint = paint.u.rotate.paint;
1333 break;
1334 }
1335
1336 case FT_COLR_PAINTFORMAT_SKEW:
1337 {
1338 qreal centerX = FROM_FIXED_16_16(paint.u.skew.center_x);
1339 qreal centerY = -FROM_FIXED_16_16(paint.u.skew.center_y);
1340 qreal angleX = FROM_FIXED_16_16(paint.u.skew.x_skew_angle) * M_PI;
1341 qreal angleY = -FROM_FIXED_16_16(paint.u.skew.y_skew_angle) * M_PI;
1342
1343 xform.translate(centerX, centerY);
1344 xform.shear(qTan(angleX), qTan(angleY));
1345 xform.translate(-centerX, -centerY);
1346
1347 nextPaint = paint.u.rotate.paint;
1348 break;
1349 }
1350 case FT_COLR_PAINTFORMAT_TRANSLATE:
1351 {
1352 qreal dx = FROM_FIXED_16_16(paint.u.translate.dx);
1353 qreal dy = -FROM_FIXED_16_16(paint.u.translate.dy);
1354
1355 xform.translate(dx, dy);
1356 nextPaint = paint.u.rotate.paint;
1357 break;
1358 }
1359 default:
1360 Q_UNREACHABLE();
1361 };
1362
1363 paintGraphRenderer->prependTransform(xform);
1364 if (!traverseColr1(nextPaint, loops, foregroundColor, palette, paletteCount, paintGraphRenderer))
1365 return false;
1366 } else if (paint.format == FT_COLR_PAINTFORMAT_LINEAR_GRADIENT
1367 || paint.format == FT_COLR_PAINTFORMAT_RADIAL_GRADIENT
1368 || paint.format == FT_COLR_PAINTFORMAT_SWEEP_GRADIENT
1369 || paint.format == FT_COLR_PAINTFORMAT_SOLID) {
1370 auto getPaletteColor = [&palette, &paletteCount, &foregroundColor](FT_UInt16 index,
1371 FT_F2Dot14 alpha) {
1372 QColor color;
1373 if (index < paletteCount) {
1374 const FT_Color &paletteColor = palette[index];
1375 color = qRgba(paletteColor.red,
1376 paletteColor.green,
1377 paletteColor.blue,
1378 paletteColor.alpha);
1379 } else if (index == 0xffff) {
1380 color = foregroundColor;
1381 }
1382
1383 if (color.isValid())
1384 color.setAlphaF(color.alphaF() * (alpha / 16384.0));
1385
1386 return color;
1387 };
1388
1389 auto gatherGradientStops = [&](FT_ColorStopIterator it) {
1390 QGradientStops ret;
1391 ret.resize(it.num_color_stops);
1392
1393 FT_ColorStop colorStop;
1394 while (FT_Get_Colorline_Stops(face, &colorStop, &it)) {
1395 uint index = it.current_color_stop - 1;
1396 if (qsizetype(index) < ret.size()) {
1397 QGradientStop &gradientStop = ret[index];
1398 gradientStop.first = FROM_FIXED_16_16(colorStop.stop_offset);
1399 gradientStop.second = getPaletteColor(colorStop.color.palette_index,
1400 colorStop.color.alpha);
1401 }
1402 }
1403
1404 return ret;
1405 };
1406
1407 auto extendToSpread = [](FT_PaintExtend extend) {
1408 switch (extend) {
1409 case FT_COLR_PAINT_EXTEND_REPEAT:
1410 return QGradient::RepeatSpread;
1411 case FT_COLR_PAINT_EXTEND_REFLECT:
1412 return QGradient::ReflectSpread;
1413 default:
1414 return QGradient::PadSpread;
1415 }
1416 };
1417
1418 if (paintGraphRenderer->isRendering()) {
1419 if (paint.format == FT_COLR_PAINTFORMAT_LINEAR_GRADIENT) {
1420 const qreal p0x = FROM_FIXED_16_16(paint.u.linear_gradient.p0.x);
1421 const qreal p0y = -FROM_FIXED_16_16(paint.u.linear_gradient.p0.y);
1422
1423 const qreal p1x = FROM_FIXED_16_16(paint.u.linear_gradient.p1.x);
1424 const qreal p1y = -FROM_FIXED_16_16(paint.u.linear_gradient.p1.y);
1425
1426 const qreal p2x = FROM_FIXED_16_16(paint.u.linear_gradient.p2.x);
1427 const qreal p2y = -FROM_FIXED_16_16(paint.u.linear_gradient.p2.y);
1428
1429 QPointF p0(p0x, p0y);
1430 QPointF p1(p1x, p1y);
1431 QPointF p2(p2x, p2y);
1432
1433 const QGradient::Spread spread =
1434 extendToSpread(paint.u.linear_gradient.colorline.extend);
1435 const QGradientStops stops =
1436 gatherGradientStops(paint.u.linear_gradient.colorline.color_stop_iterator);
1437 paintGraphRenderer->setLinearGradient(p0, p1, p2, spread, stops);
1438
1439 } else if (paint.format == FT_COLR_PAINTFORMAT_RADIAL_GRADIENT) {
1440 const qreal c0x = FROM_FIXED_16_16(paint.u.radial_gradient.c0.x);
1441 const qreal c0y = -FROM_FIXED_16_16(paint.u.radial_gradient.c0.y);
1442 const qreal r0 = FROM_FIXED_16_16(paint.u.radial_gradient.r0);
1443 const qreal c1x = FROM_FIXED_16_16(paint.u.radial_gradient.c1.x);
1444 const qreal c1y = -FROM_FIXED_16_16(paint.u.radial_gradient.c1.y);
1445 const qreal r1 = FROM_FIXED_16_16(paint.u.radial_gradient.r1);
1446
1447 const QPointF c0(c0x, c0y);
1448 const QPointF c1(c1x, c1y);
1449 const QGradient::Spread spread =
1450 extendToSpread(paint.u.radial_gradient.colorline.extend);
1451 const QGradientStops stops =
1452 gatherGradientStops(paint.u.radial_gradient.colorline.color_stop_iterator);
1453
1454 paintGraphRenderer->setRadialGradient(c0, r0, c1, r1, spread, stops);
1455 } else if (paint.format == FT_COLR_PAINTFORMAT_SWEEP_GRADIENT) {
1456 const qreal centerX = FROM_FIXED_16_16(paint.u.sweep_gradient.center.x);
1457 const qreal centerY = -FROM_FIXED_16_16(paint.u.sweep_gradient.center.y);
1458 const qreal startAngle = 180.0 * FROM_FIXED_16_16(paint.u.sweep_gradient.start_angle);
1459 const qreal endAngle = 180.0 * FROM_FIXED_16_16(paint.u.sweep_gradient.end_angle);
1460
1461 const QPointF center(centerX, centerY);
1462
1463 const QGradient::Spread spread = extendToSpread(paint.u.radial_gradient.colorline.extend);
1464 const QGradientStops stops = gatherGradientStops(paint.u.sweep_gradient.colorline.color_stop_iterator);
1465
1466 paintGraphRenderer->setConicalGradient(center, startAngle, endAngle, spread, stops);
1467
1468 } else if (paint.format == FT_COLR_PAINTFORMAT_SOLID) {
1469 QColor color = getPaletteColor(paint.u.solid.color.palette_index,
1470 paint.u.solid.color.alpha);
1471 if (!color.isValid()) {
1472 qCWarning(lcColrv1) << "Invalid palette index in COLRv1 graph:"
1473 << paint.u.solid.color.palette_index;
1474 return false;
1475 }
1476
1477 paintGraphRenderer->setSolidColor(color);
1478 }
1479 }
1480
1481 paintGraphRenderer->drawCurrentPath();
1482 } else if (paint.format == FT_COLR_PAINTFORMAT_COMPOSITE) {
1483 if (!paintGraphRenderer->isRendering()) {
1484 if (!traverseColr1(paint.u.composite.backdrop_paint,
1485 loops,
1486 foregroundColor,
1487 palette,
1488 paletteCount,
1489 paintGraphRenderer)) {
1490 return false;
1491 }
1492 if (!traverseColr1(paint.u.composite.source_paint,
1493 loops,
1494 foregroundColor,
1495 palette,
1496 paletteCount,
1497 paintGraphRenderer)) {
1498 return false;
1499 }
1500 } else {
1501 QPainter::CompositionMode compositionMode = QPainter::CompositionMode_SourceOver;
1502 switch (paint.u.composite.composite_mode) {
1503 case FT_COLR_COMPOSITE_CLEAR:
1504 compositionMode = QPainter::CompositionMode_Clear;
1505 break;
1506 case FT_COLR_COMPOSITE_SRC:
1507 compositionMode = QPainter::CompositionMode_Source;
1508 break;
1509 case FT_COLR_COMPOSITE_DEST:
1510 compositionMode = QPainter::CompositionMode_Destination;
1511 break;
1512 case FT_COLR_COMPOSITE_SRC_OVER:
1513 compositionMode = QPainter::CompositionMode_SourceOver;
1514 break;
1515 case FT_COLR_COMPOSITE_DEST_OVER:
1516 compositionMode = QPainter::CompositionMode_DestinationOver;
1517 break;
1518 case FT_COLR_COMPOSITE_SRC_IN:
1519 compositionMode = QPainter::CompositionMode_SourceIn;
1520 break;
1521 case FT_COLR_COMPOSITE_DEST_IN:
1522 compositionMode = QPainter::CompositionMode_DestinationIn;
1523 break;
1524 case FT_COLR_COMPOSITE_SRC_OUT:
1525 compositionMode = QPainter::CompositionMode_SourceOut;
1526 break;
1527 case FT_COLR_COMPOSITE_DEST_OUT:
1528 compositionMode = QPainter::CompositionMode_DestinationOut;
1529 break;
1530 case FT_COLR_COMPOSITE_SRC_ATOP:
1531 compositionMode = QPainter::CompositionMode_SourceAtop;
1532 break;
1533 case FT_COLR_COMPOSITE_DEST_ATOP:
1534 compositionMode = QPainter::CompositionMode_DestinationAtop;
1535 break;
1536 case FT_COLR_COMPOSITE_XOR:
1537 compositionMode = QPainter::CompositionMode_Xor;
1538 break;
1539 case FT_COLR_COMPOSITE_PLUS:
1540 compositionMode = QPainter::CompositionMode_Plus;
1541 break;
1542 case FT_COLR_COMPOSITE_SCREEN:
1543 compositionMode = QPainter::CompositionMode_Screen;
1544 break;
1545 case FT_COLR_COMPOSITE_OVERLAY:
1546 compositionMode = QPainter::CompositionMode_Overlay;
1547 break;
1548 case FT_COLR_COMPOSITE_DARKEN:
1549 compositionMode = QPainter::CompositionMode_Darken;
1550 break;
1551 case FT_COLR_COMPOSITE_LIGHTEN:
1552 compositionMode = QPainter::CompositionMode_Lighten;
1553 break;
1554 case FT_COLR_COMPOSITE_COLOR_DODGE:
1555 compositionMode = QPainter::CompositionMode_ColorDodge;
1556 break;
1557 case FT_COLR_COMPOSITE_COLOR_BURN:
1558 compositionMode = QPainter::CompositionMode_ColorBurn;
1559 break;
1560 case FT_COLR_COMPOSITE_HARD_LIGHT:
1561 compositionMode = QPainter::CompositionMode_HardLight;
1562 break;
1563 case FT_COLR_COMPOSITE_SOFT_LIGHT:
1564 compositionMode = QPainter::CompositionMode_SoftLight;
1565 break;
1566 case FT_COLR_COMPOSITE_DIFFERENCE:
1567 compositionMode = QPainter::CompositionMode_Difference;
1568 break;
1569 case FT_COLR_COMPOSITE_EXCLUSION:
1570 compositionMode = QPainter::CompositionMode_Exclusion;
1571 break;
1572 case FT_COLR_COMPOSITE_MULTIPLY:
1573 compositionMode = QPainter::CompositionMode_Multiply;
1574 break;
1575 default:
1576 qCWarning(lcColrv1) << "Unsupported COLRv1 composition mode" << paint.u.composite.composite_mode;
1577 break;
1578 };
1579
1580 QColrPaintGraphRenderer compositeRenderer;
1581 compositeRenderer.setBoundingRect(paintGraphRenderer->boundingRect());
1582 compositeRenderer.beginRender(fontDef.pixelSize / face->units_per_EM,
1583 paintGraphRenderer->currentTransform());
1584 if (!traverseColr1(paint.u.composite.backdrop_paint,
1585 loops,
1586 foregroundColor,
1587 palette,
1588 paletteCount,
1589 &compositeRenderer)) {
1590 return false;
1591 }
1592
1593 compositeRenderer.setCompositionMode(compositionMode);
1594 if (!traverseColr1(paint.u.composite.source_paint,
1595 loops,
1596 foregroundColor,
1597 palette,
1598 paletteCount,
1599 &compositeRenderer)) {
1600 return false;
1601 }
1602 paintGraphRenderer->drawImage(compositeRenderer.endRender());
1603 }
1604 } else if (paint.format == FT_COLR_PAINTFORMAT_GLYPH) {
1605 FT_Error error = FT_Load_Glyph(face,
1606 paint.u.glyph.glyphID,
1607 FT_LOAD_DEFAULT | FT_LOAD_NO_BITMAP | FT_LOAD_NO_SVG | FT_LOAD_IGNORE_TRANSFORM | FT_LOAD_NO_HINTING | FT_LOAD_NO_AUTOHINT | FT_LOAD_BITMAP_METRICS_ONLY);
1608 if (error) {
1609 qCWarning(lcColrv1) << "Failed to load glyph"
1610 << paint.u.glyph.glyphID
1611 << "in COLRv1 graph. Error: " << error;
1612 return false;
1613 }
1614
1615 QPainterPath path;
1616 QFreetypeFace::addGlyphToPath(face,
1617 face->glyph,
1618 QFixedPoint(0, 0),
1619 &path,
1620 face->units_per_EM << 6,
1621 face->units_per_EM << 6);
1622
1623 paintGraphRenderer->appendPath(path);
1624
1625 if (!traverseColr1(paint.u.glyph.paint, loops, foregroundColor, palette, paletteCount, paintGraphRenderer))
1626 return false;
1627 } else if (paint.format == FT_COLR_PAINTFORMAT_COLR_GLYPH) {
1628 FT_OpaquePaint otherOpaquePaint;
1629 otherOpaquePaint.p = nullptr;
1630 if (!FT_Get_Color_Glyph_Paint(face,
1631 paint.u.colr_glyph.glyphID,
1632 FT_COLOR_NO_ROOT_TRANSFORM,
1633 &otherOpaquePaint)) {
1634 qCWarning(lcColrv1) << "Failed to load color glyph"
1635 << paint.u.colr_glyph.glyphID
1636 << "in COLRv1 graph.";
1637 return false;
1638 }
1639
1640 if (!traverseColr1(otherOpaquePaint, loops, foregroundColor, palette, paletteCount, paintGraphRenderer))
1641 return false;
1642 }
1643
1644 return true;
1645}
1646
1647QFontEngineFT::Glyph *QFontEngineFT::loadColrv1Glyph(QGlyphSet *set,
1648 Glyph *g,
1649 uint glyph,
1650 const QColor &foregroundColor,
1651 bool fetchMetricsOnly) const
1652{
1653 FT_Face face = freetype->face;
1654
1655 GlyphInfo info;
1656 memset(&info, 0, sizeof(info));
1657
1658 // Load advance metrics for glyph. As documented, these should come from the base
1659 // glyph record.
1660 FT_Load_Glyph(face, glyph, FT_LOAD_DEFAULT
1661 | FT_LOAD_NO_BITMAP
1662 | FT_LOAD_NO_SVG
1663 | FT_LOAD_BITMAP_METRICS_ONLY);
1664 info.linearAdvance = int(face->glyph->linearHoriAdvance >> 10);
1665 info.xOff = short(TRUNC(ROUND(face->glyph->advance.x)));
1666
1667 FT_OpaquePaint opaquePaint;
1668 opaquePaint.p = nullptr;
1669 if (!FT_Get_Color_Glyph_Paint(face, glyph, FT_COLOR_INCLUDE_ROOT_TRANSFORM, &opaquePaint))
1670 return nullptr;
1671
1672 // The scene graph is in design coordinate system, so we need to also get glyphs in this
1673 // coordinate system. We then scale all painting to the requested pixel size
1674 FT_Set_Char_Size(face, face->units_per_EM << 6, face->units_per_EM << 6, 0, 0);
1675
1676 FT_Matrix matrix;
1677 FT_Vector delta;
1678 FT_Get_Transform(face, &matrix, &delta);
1679 QTransform originalXform(FROM_FIXED_16_16(matrix.xx), -FROM_FIXED_16_16(matrix.yx),
1680 -FROM_FIXED_16_16(matrix.xy), FROM_FIXED_16_16(matrix.yy),
1681 FROM_FIXED_16_16(delta.x), FROM_FIXED_16_16(delta.y));
1682
1683
1684 // Also clear transform to ensure we operate in design metrics
1685 FT_Set_Transform(face, nullptr, nullptr);
1686
1687 auto cleanup = qScopeGuard([&]() {
1688 // Reset stuff we changed
1689 FT_Set_Char_Size(face, xsize, ysize, 0, 0);
1690 FT_Set_Transform(face, &matrix, &delta);
1691 });
1692
1693 qCDebug(lcColrv1).noquote() << "================== Start collecting COLRv1 metrics for" << glyph;
1694 QRect designCoordinateBounds;
1695
1696 // Getting metrics is done multiple times per glyph while entering it into the cache.
1697 // Since this may need to be calculated, we cache the last one for sequential calls.
1698 if (colrv1_bounds_cache_id == glyph) {
1699 designCoordinateBounds = colrv1_bounds_cache;
1700 } else {
1701 // COLRv1 fonts can optionally have a clip box for quicker retrieval of metrics. We try
1702 // to get this, and if there is none, we calculate the bounds by traversing the graph.
1703 FT_ClipBox clipBox;
1704 if (FT_Get_Color_Glyph_ClipBox(face, glyph, &clipBox)) {
1705 FT_Pos left = qMin(clipBox.bottom_left.x, qMin(clipBox.bottom_right.x, qMin(clipBox.top_left.x, clipBox.top_right.x)));
1706 FT_Pos right = qMax(clipBox.bottom_left.x, qMax(clipBox.bottom_right.x, qMax(clipBox.top_left.x, clipBox.top_right.x)));
1707
1708 FT_Pos top = qMin(-clipBox.bottom_left.y, qMin(-clipBox.bottom_right.y, qMin(-clipBox.top_left.y, -clipBox.top_right.y)));
1709 FT_Pos bottom = qMax(-clipBox.bottom_left.y, qMax(-clipBox.bottom_right.y, qMax(-clipBox.top_left.y, -clipBox.top_right.y)));
1710
1711 qreal scale = 1.0 / 64.0;
1712 designCoordinateBounds = QRect(QPoint(qFloor(left * scale), qFloor(top * scale)),
1713 QPoint(qCeil(right * scale), qCeil(bottom * scale)));
1714 } else {
1715 // Do a pass over the graph to find the bounds
1716 QColrPaintGraphRenderer boundingRectCalculator;
1717 boundingRectCalculator.beginCalculateBoundingBox();
1718 QSet<std::pair<FT_Byte *, FT_Bool> > loops;
1719 if (traverseColr1(opaquePaint,
1720 &loops,
1721 QColor{},
1722 nullptr,
1723 0,
1724 &boundingRectCalculator)) {
1725 designCoordinateBounds = boundingRectCalculator.boundingRect().toAlignedRect();
1726 }
1727 }
1728
1729 colrv1_bounds_cache_id = glyph;
1730 colrv1_bounds_cache = designCoordinateBounds;
1731 }
1732
1733 QTransform initialTransform;
1734 initialTransform.scale(fontDef.pixelSize / face->units_per_EM,
1735 fontDef.pixelSize / face->units_per_EM);
1736 QRect bounds = initialTransform.mapRect(designCoordinateBounds);
1737 bounds = originalXform.mapRect(bounds);
1738
1739 info.x = bounds.left();
1740 info.y = -bounds.top();
1741 info.width = bounds.width();
1742 info.height = bounds.height();
1743
1744 qCDebug(lcColrv1) << "Bounds of" << glyph << "==" << bounds;
1745
1746 // If requested, we now render the scene graph into an image using QPainter
1747 QImage destinationImage;
1748 if (!fetchMetricsOnly && !bounds.size().isEmpty()) {
1749 FT_Palette_Data paletteData;
1750 if (FT_Palette_Data_Get(face, &paletteData))
1751 return nullptr;
1752
1753 FT_Color *palette = nullptr;
1754 FT_Error error = FT_Palette_Select(face, 0, &palette);
1755 if (error) {
1756 qWarning("selecting palette for COLRv1 failed, err=%x face=%p, glyph=%d",
1757 error,
1758 face,
1759 glyph);
1760 }
1761
1762 if (palette == nullptr)
1763 return nullptr;
1764
1765 ushort paletteCount = paletteData.num_palette_entries;
1766
1767 QColrPaintGraphRenderer paintGraphRenderer;
1768 paintGraphRenderer.setBoundingRect(bounds);
1769 paintGraphRenderer.beginRender(fontDef.pixelSize / face->units_per_EM,
1770 originalXform);
1771
1772 // Render
1773 QSet<std::pair<FT_Byte *, FT_Bool> > loops;
1774 if (!traverseColr1(opaquePaint,
1775 &loops,
1776 foregroundColor,
1777 palette,
1778 paletteCount,
1779 &paintGraphRenderer)) {
1780 return nullptr;
1781 }
1782
1783 destinationImage = paintGraphRenderer.endRender();
1784 }
1785
1786 if (fetchMetricsOnly || !destinationImage.isNull()) {
1787 if (g == nullptr) {
1788 g = new Glyph;
1789 g->data = nullptr;
1790 if (set != nullptr)
1791 set->setGlyph(glyph, QFixedPoint{}, g);
1792 }
1793
1794 g->linearAdvance = info.linearAdvance;
1795 g->width = info.width;
1796 g->height = info.height;
1797 g->x = info.x;
1798 g->y = info.y;
1799 g->advance = info.xOff;
1800 g->format = Format_ARGB;
1801
1802 if (!fetchMetricsOnly && !destinationImage.isNull()) {
1803 g->data = new uchar[info.height * info.width * 4];
1804 memcpy(g->data, destinationImage.constBits(), info.height * info.width * 4);
1805 }
1806
1807 return g;
1808 }
1809
1810 return nullptr;
1811}
1812#endif // QFONTENGINE_FT_SUPPORT_COLRV1
1813
1814// The slant of FT_GlyphSlot_Oblique(), this has to be kept in sync with FreeType
1815static const FT_Fixed obliqueSlant = 0x0366A;
1816
1817QFontEngineFT::Glyph *QFontEngineFT::loadGlyph(QGlyphSet *set, uint glyph,
1818 const QFixedPoint &subPixelPosition,
1819 QColor color,
1820 GlyphFormat format,
1821 bool fetchMetricsOnly,
1822 bool disableOutlineDrawing) const
1823{
1824// Q_ASSERT(freetype->lock == 1);
1825
1826 if (format == Format_None)
1827 format = defaultFormat != Format_None ? defaultFormat : Format_Mono;
1828 Q_ASSERT(format != Format_None);
1829
1830 Glyph *g = set ? set->getGlyph(glyph, subPixelPosition) : nullptr;
1831 if (g && g->format == format && (fetchMetricsOnly || g->data))
1832 return g;
1833
1834 if (!g && set && set->isGlyphMissing(glyph))
1835 return &emptyGlyph;
1836
1837
1838 FT_Face face = freetype->face;
1839
1840 FT_Matrix matrix = freetype->matrix;
1841 bool transform = matrix.xx != 0x10000
1842 || matrix.yy != 0x10000
1843 || matrix.xy != 0
1844 || matrix.yx != 0;
1845 if (obliquen && transform) {
1846 // We have to apply the obliquen transformation before any
1847 // other transforms. This means we need to duplicate Freetype's
1848 // obliquen matrix here and this has to be kept in sync.
1849 FT_Matrix slant;
1850 slant.xx = 0x10000L;
1851 slant.yx = 0;
1852 slant.xy = obliqueSlant;
1853 slant.yy = 0x10000L;
1854
1855 FT_Matrix_Multiply(&matrix, &slant);
1856 matrix = slant;
1857 }
1858
1859 FT_Vector v;
1860 v.x = format == Format_Mono ? 0 : FT_Pos(subPixelPosition.x.value());
1861 v.y = format == Format_Mono ? 0 : FT_Pos(-subPixelPosition.y.value());
1862 FT_Set_Transform(face, &matrix, &v);
1863
1864 bool hsubpixel = false;
1865 int vfactor = 1;
1866 int load_flags = loadFlags(set, format, 0, hsubpixel, vfactor);
1867
1868 if (transform || obliquen || (format != Format_Mono && !isScalableBitmap()))
1869 load_flags |= FT_LOAD_NO_BITMAP;
1870
1871#if (FREETYPE_MAJOR*10000 + FREETYPE_MINOR*100 + FREETYPE_PATCH) >= 20701
1872 // Only the advance/metrics are needed here, not the rendered bitmap
1873 if (fetchMetricsOnly)
1874 load_flags |= FT_LOAD_BITMAP_METRICS_ONLY;
1875#endif
1876
1877#if defined(QFONTENGINE_FT_SUPPORT_COLRV1)
1878 if (FT_IS_SCALABLE(freetype->face)
1879 && FT_HAS_COLOR(freetype->face)
1880 && (load_flags & FT_LOAD_COLOR)) {
1881 // Try loading COLRv1 glyph if possible.
1882 Glyph *ret = loadColrv1Glyph(set, g, glyph, color, fetchMetricsOnly);
1883 if (ret != nullptr)
1884 return ret;
1885 }
1886#else
1887 Q_UNUSED(color);
1888#endif
1889
1890 FT_Error err = FT_Load_Glyph(face, glyph, load_flags);
1891 if (err && (load_flags & FT_LOAD_NO_BITMAP)) {
1892 load_flags &= ~FT_LOAD_NO_BITMAP;
1893 err = FT_Load_Glyph(face, glyph, load_flags);
1894 }
1895 if (err == FT_Err_Too_Few_Arguments) {
1896 // this is an error in the bytecode interpreter, just try to run without it
1897 load_flags |= FT_LOAD_FORCE_AUTOHINT;
1898 err = FT_Load_Glyph(face, glyph, load_flags);
1899 } else if (err == FT_Err_Execution_Too_Long) {
1900 // This is an error in the bytecode, probably a web font made by someone who
1901 // didn't test bytecode hinting at all so disable for it for all glyphs.
1902 qWarning("load glyph failed due to broken hinting bytecode in font, switching to auto hinting");
1903 default_load_flags |= FT_LOAD_FORCE_AUTOHINT;
1904 load_flags |= FT_LOAD_FORCE_AUTOHINT;
1905 err = FT_Load_Glyph(face, glyph, load_flags);
1906 }
1907 if (err != FT_Err_Ok) {
1908 qWarning("load glyph failed err=%x face=%p, glyph=%d", err, face, glyph);
1909 if (set)
1910 set->setGlyphMissing(glyph);
1911 return &emptyGlyph;
1912 }
1913
1914 FT_GlyphSlot slot = face->glyph;
1915
1916 if (embolden)
1917 FT_GlyphSlot_Embolden(slot);
1918 if (obliquen && !transform) {
1919 FT_GlyphSlot_Oblique(slot);
1920
1921 // While Embolden alters the metrics of the slot, oblique does not, so we need
1922 // to fix this ourselves.
1923 transform = true;
1924 FT_Matrix m;
1925 m.xx = 0x10000;
1926 m.yx = 0x0;
1927 m.xy = 0x6000;
1928 m.yy = 0x10000;
1929
1930 FT_Matrix_Multiply(&m, &matrix);
1931 }
1932
1933 GlyphInfo info;
1934 info.linearAdvance = slot->linearHoriAdvance >> 10;
1935 info.xOff = TRUNC(ROUND(slot->advance.x));
1936 info.yOff = 0;
1937
1938 if ((set && set->outline_drawing && !disableOutlineDrawing) || fetchMetricsOnly) {
1939 int left = slot->metrics.horiBearingX;
1940 int right = slot->metrics.horiBearingX + slot->metrics.width;
1941 int top = slot->metrics.horiBearingY;
1942 int bottom = slot->metrics.horiBearingY - slot->metrics.height;
1943
1944 if (transform && slot->format != FT_GLYPH_FORMAT_BITMAP)
1945 transformBoundingBox(&left, &top, &right, &bottom, &matrix);
1946
1947 left = FLOOR(left);
1948 right = CEIL(right);
1949 bottom = FLOOR(bottom);
1950 top = CEIL(top);
1951
1952 info.x = TRUNC(left);
1953 info.y = TRUNC(top);
1954 info.width = TRUNC(right - left);
1955 info.height = TRUNC(top - bottom);
1956
1957 // If any of the metrics are too large to fit, don't cache them
1958 // Also, avoid integer overflow when linearAdvance is to large to fit in a signed short
1959 if (areMetricsTooLarge(info))
1960 return nullptr;
1961
1962 g = new Glyph;
1963 g->data = nullptr;
1964 g->linearAdvance = info.linearAdvance;
1965 g->width = info.width;
1966 g->height = info.height;
1967 g->x = info.x;
1968 g->y = info.y;
1969 g->advance = info.xOff;
1970 g->format = format;
1971
1972 if (set)
1973 set->setGlyph(glyph, subPixelPosition, g);
1974
1975 return g;
1976 }
1977
1978 if (slot->format == FT_GLYPH_FORMAT_OUTLINE) {
1979 const QTransform xform(matrix.xx / 65536.0, matrix.xy / 65536.0,
1980 matrix.yx / 65536.0, matrix.yy / 65536.0, 0, 0);
1981 FT_BBox cbox;
1982 FT_Outline_Get_CBox(&slot->outline, &cbox);
1983 const qreal glyphWidth = qreal((cbox.xMax - cbox.xMin) >> 6);
1984 const qreal glyphHeight = qreal((cbox.yMax - cbox.yMin) >> 6);
1985 if (glyphBoundingBoxExceedsLimit(glyphWidth, glyphHeight, effectivePixelSizeSquared(xform))) {
1986 if (set)
1987 set->setGlyphMissing(glyph);
1988 return &emptyGlyph;
1989 }
1990 }
1991
1992 int glyph_buffer_size = 0;
1993 std::unique_ptr<uchar[]> glyph_buffer;
1994 FT_Render_Mode renderMode = (default_hint_style == HintLight) ? FT_RENDER_MODE_LIGHT : FT_RENDER_MODE_NORMAL;
1995 switch (format) {
1996 case Format_Mono:
1997 renderMode = FT_RENDER_MODE_MONO;
1998 break;
1999 case Format_A32:
2000 if (!hsubpixel && vfactor == 1) {
2001 qWarning("Format_A32 requested, but subpixel layout is unknown.");
2002 return nullptr;
2003 }
2004
2005 renderMode = hsubpixel ? FT_RENDER_MODE_LCD : FT_RENDER_MODE_LCD_V;
2006 break;
2007 case Format_A8:
2008 case Format_ARGB:
2009 break;
2010 default:
2011 Q_UNREACHABLE();
2012 }
2013 FT_Library_SetLcdFilter(slot->library, (FT_LcdFilter)lcdFilterType);
2014
2015 err = FT_Render_Glyph(slot, renderMode);
2016 FT_Library_SetLcdFilter(slot->library, FT_LCD_FILTER_NONE);
2017
2018 if (err != FT_Err_Ok) {
2019 qWarning("render glyph failed err=%x face=%p, glyph=%d", err, face, glyph);
2020 return nullptr;
2021 }
2022
2023 info.height = slot->bitmap.rows;
2024 info.width = slot->bitmap.width;
2025 info.x = slot->bitmap_left;
2026 info.y = slot->bitmap_top;
2027 if (slot->bitmap.pixel_mode == FT_PIXEL_MODE_LCD)
2028 info.width = info.width / 3;
2029 if (slot->bitmap.pixel_mode == FT_PIXEL_MODE_LCD_V)
2030 info.height = info.height / vfactor;
2031
2032 int pitch = (format == Format_Mono ? ((info.width + 31) & ~31) >> 3 :
2033 (format == Format_A8 ? (info.width + 3) & ~3 : info.width * 4));
2034
2035 glyph_buffer_size = info.height * pitch;
2036 glyph_buffer.reset(new uchar[glyph_buffer_size]);
2037
2038 if (slot->bitmap.pixel_mode == FT_PIXEL_MODE_MONO) {
2039 uchar *src = slot->bitmap.buffer;
2040 uchar *dst = glyph_buffer.get();
2041 int h = slot->bitmap.rows;
2042 // Some fonts return bitmaps even when we requested something else:
2043 if (format == Format_Mono) {
2044 int bytes = ((info.width + 7) & ~7) >> 3;
2045 while (h--) {
2046 memcpy (dst, src, bytes);
2047 dst += pitch;
2048 src += slot->bitmap.pitch;
2049 }
2050 } else if (format == Format_A8) {
2051 while (h--) {
2052 for (int x = 0; x < int{info.width}; x++)
2053 dst[x] = ((src[x >> 3] & (0x80 >> (x & 7))) ? 0xff : 0x00);
2054 dst += pitch;
2055 src += slot->bitmap.pitch;
2056 }
2057 } else {
2058 while (h--) {
2059 uint *dd = reinterpret_cast<uint *>(dst);
2060 for (int x = 0; x < int{info.width}; x++)
2061 dd[x] = ((src[x >> 3] & (0x80 >> (x & 7))) ? 0xffffffff : 0x00000000);
2062 dst += pitch;
2063 src += slot->bitmap.pitch;
2064 }
2065 }
2066 } else if (slot->bitmap.pixel_mode == 7 /*FT_PIXEL_MODE_BGRA*/) {
2067 Q_ASSERT(format == Format_ARGB);
2068 uchar *src = slot->bitmap.buffer;
2069 uchar *dst = glyph_buffer.get();
2070 int h = slot->bitmap.rows;
2071 while (h--) {
2072#if Q_BYTE_ORDER == Q_BIG_ENDIAN
2073 const quint32 *srcPixel = (const quint32 *)src;
2074 quint32 *dstPixel = (quint32 *)dst;
2075 for (int x = 0; x < static_cast<int>(slot->bitmap.width); x++, srcPixel++, dstPixel++) {
2076 const quint32 pixel = *srcPixel;
2077 *dstPixel = qbswap(pixel);
2078 }
2079#else
2080 memcpy(dst, src, slot->bitmap.width * 4);
2081#endif
2082 dst += slot->bitmap.pitch;
2083 src += slot->bitmap.pitch;
2084 }
2085 info.linearAdvance = info.xOff = slot->bitmap.width;
2086 } else if (slot->bitmap.pixel_mode == FT_PIXEL_MODE_GRAY) {
2087 if (format == Format_A8) {
2088 uchar *src = slot->bitmap.buffer;
2089 uchar *dst = glyph_buffer.get();
2090 int h = slot->bitmap.rows;
2091 int bytes = info.width;
2092 while (h--) {
2093 memcpy (dst, src, bytes);
2094 dst += pitch;
2095 src += slot->bitmap.pitch;
2096 }
2097 } else if (format == Format_ARGB) {
2098 uchar *src = slot->bitmap.buffer;
2099 quint32 *dstPixel = reinterpret_cast<quint32 *>(glyph_buffer.get());
2100 int h = slot->bitmap.rows;
2101 while (h--) {
2102 for (int x = 0; x < static_cast<int>(slot->bitmap.width); ++x) {
2103 uchar alpha = src[x];
2104 float alphaF = alpha / 255.0;
2105 dstPixel[x] = qRgba(qRound(alphaF * color.red()),
2106 qRound(alphaF * color.green()),
2107 qRound(alphaF * color.blue()),
2108 alpha);
2109 }
2110 src += slot->bitmap.pitch;
2111 dstPixel += info.width;
2112 }
2113 }
2114 } else if (slot->bitmap.pixel_mode == FT_PIXEL_MODE_LCD) {
2115 Q_ASSERT(format == Format_A32);
2116 convertRGBToARGB(slot->bitmap.buffer, (uint *)glyph_buffer.get(), info.width, info.height, slot->bitmap.pitch, subpixelType != Subpixel_RGB);
2117 } else if (slot->bitmap.pixel_mode == FT_PIXEL_MODE_LCD_V) {
2118 Q_ASSERT(format == Format_A32);
2119 convertRGBToARGB_V(slot->bitmap.buffer, (uint *)glyph_buffer.get(), info.width, info.height, slot->bitmap.pitch, subpixelType != Subpixel_VRGB);
2120 } else {
2121 qWarning("QFontEngine: Glyph rendered in unknown pixel_mode=%d", slot->bitmap.pixel_mode);
2122 return nullptr;
2123 }
2124
2125 if (!g) {
2126 g = new Glyph;
2127 g->data = nullptr;
2128 }
2129
2130 g->linearAdvance = info.linearAdvance;
2131 g->width = info.width;
2132 g->height = info.height;
2133 g->x = info.x;
2134 g->y = info.y;
2135 g->advance = info.xOff;
2136 g->format = format;
2137 delete [] g->data;
2138 g->data = glyph_buffer.release();
2139
2140 if (set)
2141 set->setGlyph(glyph, subPixelPosition, g);
2142
2143 return g;
2144}
2145
2146QFontEngine::FaceId QFontEngineFT::faceId() const
2147{
2148 return face_id;
2149}
2150
2151QFontEngine::Properties QFontEngineFT::properties() const
2152{
2153 Properties p = freetype->properties();
2154 if (p.postscriptName.isEmpty()) {
2155 p.postscriptName = QFontEngine::convertToPostscriptFontFamilyName(fontDef.family().toUtf8());
2156 }
2157
2158 return freetype->properties();
2159}
2160
2161QFixed QFontEngineFT::emSquareSize() const
2162{
2163 if (FT_IS_SCALABLE(freetype->face))
2164 return freetype->face->units_per_EM;
2165 else
2166 return freetype->face->size->metrics.y_ppem;
2167}
2168
2169bool QFontEngineFT::getSfntTableData(uint tag, uchar *buffer, uint *length) const
2170{
2171 return ft_getSfntTable(freetype->face, tag, buffer, length);
2172}
2173
2174int QFontEngineFT::synthesized() const
2175{
2176 int s = 0;
2177 if ((fontDef.style != QFont::StyleNormal) && !(freetype->face->style_flags & FT_STYLE_FLAG_ITALIC))
2178 s = SynthesizedItalic;
2179 if ((fontDef.weight >= QFont::Bold) && !(freetype->face->style_flags & FT_STYLE_FLAG_BOLD))
2180 s |= SynthesizedBold;
2181 if (fontDef.stretch != 100 && FT_IS_SCALABLE(freetype->face))
2182 s |= SynthesizedStretch;
2183 return s;
2184}
2185
2186void QFontEngineFT::initializeHeightMetrics() const
2187{
2188 m_ascent = QFixed::fromFixed(metrics.ascender);
2189 m_descent = QFixed::fromFixed(-metrics.descender);
2190 m_leading = QFixed::fromFixed(metrics.height - metrics.ascender + metrics.descender);
2191
2192 QFontEngine::initializeHeightMetrics();
2193
2194 if (scalableBitmapScaleFactor != 1) {
2195 m_ascent *= scalableBitmapScaleFactor;
2196 m_descent *= scalableBitmapScaleFactor;
2197 m_leading *= scalableBitmapScaleFactor;
2198 }
2199}
2200
2201QFixed QFontEngineFT::capHeight() const
2202{
2203 TT_OS2 *os2 = (TT_OS2 *)FT_Get_Sfnt_Table(freetype->face, ft_sfnt_os2);
2204 if (os2 && os2->version >= 2) {
2205 lockFace();
2206 QFixed answer = QFixed::fromFixed(FT_MulFix(os2->sCapHeight, freetype->face->size->metrics.y_scale));
2207 unlockFace();
2208 return answer;
2209 }
2210 return calculatedCapHeight();
2211}
2212
2213QFixed QFontEngineFT::xHeight() const
2214{
2215 TT_OS2 *os2 = (TT_OS2 *)FT_Get_Sfnt_Table(freetype->face, ft_sfnt_os2);
2216 if (os2 && os2->sxHeight) {
2217 lockFace();
2218 QFixed answer = QFixed(os2->sxHeight * freetype->face->size->metrics.y_ppem) / emSquareSize();
2219 unlockFace();
2220 return answer;
2221 }
2222
2223 return QFontEngine::xHeight();
2224}
2225
2226QFixed QFontEngineFT::averageCharWidth() const
2227{
2228 TT_OS2 *os2 = (TT_OS2 *)FT_Get_Sfnt_Table(freetype->face, ft_sfnt_os2);
2229 if (os2 && os2->xAvgCharWidth) {
2230 lockFace();
2231 QFixed answer = QFixed(os2->xAvgCharWidth * freetype->face->size->metrics.x_ppem) / emSquareSize();
2232 unlockFace();
2233 return answer;
2234 }
2235
2236 return QFontEngine::averageCharWidth();
2237}
2238
2239qreal QFontEngineFT::maxCharWidth() const
2240{
2241 QFixed max_advance = QFixed::fromFixed(metrics.max_advance);
2242 if (scalableBitmapScaleFactor != 1)
2243 max_advance *= scalableBitmapScaleFactor;
2244 return max_advance.toReal();
2245}
2246
2247QFixed QFontEngineFT::lineThickness() const
2248{
2249 return line_thickness;
2250}
2251
2252QFixed QFontEngineFT::underlinePosition() const
2253{
2254 return underline_position;
2255}
2256
2257void QFontEngineFT::doKerning(QGlyphLayout *g, QFontEngine::ShaperFlags flags) const
2258{
2259 if (!kerning_pairs_loaded) {
2260 kerning_pairs_loaded = true;
2261 lockFace();
2262 if (freetype->face->size->metrics.x_ppem != 0) {
2263 QFixed scalingFactor = emSquareSize() / QFixed(freetype->face->size->metrics.x_ppem);
2264 unlockFace();
2265 const_cast<QFontEngineFT *>(this)->loadKerningPairs(scalingFactor);
2266 } else {
2267 unlockFace();
2268 }
2269 }
2270
2271 if (shouldUseDesignMetrics(flags))
2272 flags |= DesignMetrics;
2273 else
2274 flags &= ~DesignMetrics;
2275
2276 QFontEngine::doKerning(g, flags);
2277}
2278
2279static inline FT_Matrix QTransformToFTMatrix(const QTransform &matrix)
2280{
2281 FT_Matrix m;
2282
2283 m.xx = FT_Fixed(matrix.m11() * 65536);
2284 m.xy = FT_Fixed(-matrix.m21() * 65536);
2285 m.yx = FT_Fixed(-matrix.m12() * 65536);
2286 m.yy = FT_Fixed(matrix.m22() * 65536);
2287
2288 return m;
2289}
2290
2291QFontEngineFT::QGlyphSet *QFontEngineFT::TransformedGlyphSets::findSet(const QTransform &matrix, const QFontDef &fontDef)
2292{
2293 FT_Matrix m = QTransformToFTMatrix(matrix);
2294
2295 int i = 0;
2296 for (; i < nSets; ++i) {
2297 QGlyphSet *g = sets[i];
2298 if (!g)
2299 break;
2300 if (g->transformationMatrix.xx == m.xx
2301 && g->transformationMatrix.xy == m.xy
2302 && g->transformationMatrix.yx == m.yx
2303 && g->transformationMatrix.yy == m.yy) {
2304
2305 // found a match, move it to the front
2306 moveToFront(i);
2307 return g;
2308 }
2309 }
2310
2311 // don't cache more than nSets transformations
2312 if (i == nSets)
2313 // reuse the last set
2314 --i;
2315 moveToFront(nSets - 1);
2316 if (!sets[0])
2317 sets[0] = new QGlyphSet;
2318 QGlyphSet *gs = sets[0];
2319 Q_ASSERT(gs != nullptr);
2320
2321 gs->clear();
2322 gs->transformationMatrix = m;
2323 const int maxCachedSize = maxCachedGlyphSize();
2324 gs->outline_drawing = fontDef.pixelSize * fontDef.pixelSize * qAbs(matrix.determinant()) > maxCachedSize * maxCachedSize;
2325
2326 return gs;
2327}
2328
2329void QFontEngineFT::TransformedGlyphSets::moveToFront(int i)
2330{
2331 QGlyphSet *g = sets[i];
2332 while (i > 0) {
2333 sets[i] = sets[i - 1];
2334 --i;
2335 }
2336 sets[0] = g;
2337}
2338
2339
2340QFontEngineFT::QGlyphSet *QFontEngineFT::loadGlyphSet(const QTransform &matrix)
2341{
2342 if (matrix.type() > QTransform::TxShear || !cacheEnabled)
2343 return nullptr;
2344
2345 // FT_Set_Transform only supports scalable fonts
2346 if (!FT_IS_SCALABLE(freetype->face))
2347 return matrix.type() <= QTransform::TxTranslate ? &defaultGlyphSet : nullptr;
2348
2349 return transformedGlyphSets.findSet(matrix, fontDef);
2350}
2351
2352void QFontEngineFT::getUnscaledGlyph(glyph_t glyph, QPainterPath *path, glyph_metrics_t *metrics)
2353{
2354 FT_Face face = lockFace(Unscaled);
2355 FT_Set_Transform(face, nullptr, nullptr);
2356 FT_Load_Glyph(face, glyph, FT_LOAD_NO_BITMAP);
2357
2358 int left = face->glyph->metrics.horiBearingX;
2359 int right = face->glyph->metrics.horiBearingX + face->glyph->metrics.width;
2360 int top = face->glyph->metrics.horiBearingY;
2361 int bottom = face->glyph->metrics.horiBearingY - face->glyph->metrics.height;
2362
2363 QFixedPoint p;
2364 p.x = 0;
2365 p.y = 0;
2366
2367 metrics->width = QFixed::fromFixed(right-left);
2368 metrics->height = QFixed::fromFixed(top-bottom);
2369 metrics->x = QFixed::fromFixed(left);
2370 metrics->y = QFixed::fromFixed(-top);
2371 metrics->xoff = QFixed::fromFixed(face->glyph->advance.x);
2372
2373 if (!FT_IS_SCALABLE(freetype->face))
2374 QFreetypeFace::addBitmapToPath(face->glyph, p, path);
2375 else
2376 QFreetypeFace::addGlyphToPath(face, face->glyph, p, path, face->units_per_EM << 6, face->units_per_EM << 6);
2377
2378 FT_Set_Transform(face, &freetype->matrix, nullptr);
2379 unlockFace();
2380}
2381
2382bool QFontEngineFT::supportsTransformation(const QTransform &transform) const
2383{
2384 return transform.type() <= QTransform::TxRotate;
2385}
2386
2387void QFontEngineFT::addOutlineToPath(qreal x, qreal y, const QGlyphLayout &glyphs, QPainterPath *path, QTextItem::RenderFlags flags)
2388{
2389 if (!glyphs.numGlyphs)
2390 return;
2391
2392 if (FT_IS_SCALABLE(freetype->face)) {
2393 QFontEngine::addOutlineToPath(x, y, glyphs, path, flags);
2394 } else {
2395 QVarLengthArray<QFixedPoint> positions;
2396 QVarLengthArray<glyph_t> positioned_glyphs;
2397 QTransform matrix;
2398 matrix.translate(x, y);
2399 getGlyphPositions(glyphs, matrix, flags, positioned_glyphs, positions);
2400
2401 FT_Face face = lockFace(Unscaled);
2402 for (int gl = 0; gl < glyphs.numGlyphs; gl++) {
2403 FT_UInt glyph = positioned_glyphs[gl];
2404 FT_Load_Glyph(face, glyph, FT_LOAD_TARGET_MONO);
2405 QFreetypeFace::addBitmapToPath(face->glyph, positions[gl], path);
2406 }
2407 unlockFace();
2408 }
2409}
2410
2411void QFontEngineFT::addGlyphsToPath(glyph_t *glyphs, QFixedPoint *positions, int numGlyphs,
2412 QPainterPath *path, QTextItem::RenderFlags)
2413{
2414 FT_Face face = lockFace(Unscaled);
2415
2416 for (int gl = 0; gl < numGlyphs; gl++) {
2417 FT_UInt glyph = glyphs[gl];
2418
2419 FT_Load_Glyph(face, glyph, FT_LOAD_NO_BITMAP);
2420
2421 FT_GlyphSlot g = face->glyph;
2422 if (g->format != FT_GLYPH_FORMAT_OUTLINE)
2423 continue;
2424 if (embolden)
2425 FT_GlyphSlot_Embolden(g);
2426 if (obliquen)
2427 FT_GlyphSlot_Oblique(g);
2428 QFreetypeFace::addGlyphToPath(face, g, positions[gl], path, xsize, ysize);
2429 }
2430 unlockFace();
2431}
2432
2433glyph_t QFontEngineFT::glyphIndex(uint ucs4) const
2434{
2435 glyph_t glyph = ucs4 < QFreetypeFace::cmapCacheSize ? freetype->cmapCache[ucs4] : 0;
2436 if (glyph == 0) {
2437 FT_Face face = freetype->face;
2438 glyph = FT_Get_Char_Index(face, ucs4);
2439 if (glyph == 0) {
2440 // Certain fonts don't have no-break space and tab,
2441 // while we usually want to render them as space
2442 if (ucs4 == QChar::Nbsp || ucs4 == QChar::Tabulation) {
2443 glyph = FT_Get_Char_Index(face, QChar::Space);
2444 } else if (freetype->symbol_map) {
2445 // Symbol fonts can have more than one CMAPs, FreeType should take the
2446 // correct one for us by default, so we always try FT_Get_Char_Index
2447 // first. If it didn't work (returns 0), we will explicitly set the
2448 // CMAP to symbol font one and try again. symbol_map is not always the
2449 // correct one because in certain fonts like Wingdings symbol_map only
2450 // contains PUA codepoints instead of the common ones.
2451 FT_Set_Charmap(face, freetype->symbol_map);
2452 glyph = FT_Get_Char_Index(face, ucs4);
2453 FT_Set_Charmap(face, freetype->unicode_map);
2454 if (!glyph && symbol && ucs4 < 0x100)
2455 glyph = FT_Get_Char_Index(face, ucs4 + 0xf000);
2456 }
2457 }
2458 if (ucs4 < QFreetypeFace::cmapCacheSize)
2459 freetype->cmapCache[ucs4] = glyph;
2460 }
2461
2462 return glyph;
2463}
2464
2465QString QFontEngineFT::glyphName(glyph_t index) const
2466{
2467 QString result;
2468 if (index >= glyph_t(glyphCount()))
2469 return result;
2470
2471 FT_Face face = freetype->face;
2472 if (face->face_flags & FT_FACE_FLAG_GLYPH_NAMES) {
2473 char glyphName[128] = {};
2474 if (FT_Get_Glyph_Name(face, index, glyphName, sizeof(glyphName)) == 0)
2475 result = QString::fromUtf8(glyphName);
2476 }
2477
2478 return result.isEmpty() ? QFontEngine::glyphName(index) : result;
2479}
2480
2481int QFontEngineFT::stringToCMap(const QChar *str, int len, QGlyphLayout *glyphs, int *nglyphs,
2482 QFontEngine::ShaperFlags flags) const
2483{
2484 Q_ASSERT(glyphs->numGlyphs >= *nglyphs);
2485 if (*nglyphs < len) {
2486 *nglyphs = len;
2487 return -1;
2488 }
2489
2490 int mappedGlyphs = 0;
2491 int glyph_pos = 0;
2492 if (freetype->symbol_map) {
2493 FT_Face face = freetype->face;
2494 QStringIterator it(str, str + len);
2495 while (it.hasNext()) {
2496 uint uc = it.next();
2497 glyphs->glyphs[glyph_pos] = uc < QFreetypeFace::cmapCacheSize ? freetype->cmapCache[uc] : 0;
2498 if ( !glyphs->glyphs[glyph_pos] ) {
2499 // Symbol fonts can have more than one CMAPs, FreeType should take the
2500 // correct one for us by default, so we always try FT_Get_Char_Index
2501 // first. If it didn't work (returns 0), we will explicitly set the
2502 // CMAP to symbol font one and try again. symbol_map is not always the
2503 // correct one because in certain fonts like Wingdings symbol_map only
2504 // contains PUA codepoints instead of the common ones.
2505 glyph_t glyph = FT_Get_Char_Index(face, uc);
2506 // Certain symbol fonts don't have no-break space (0xa0) and tab (0x9),
2507 // while we usually want to render them as space
2508 if (!glyph && (uc == 0xa0 || uc == 0x9)) {
2509 uc = 0x20;
2510 glyph = FT_Get_Char_Index(face, uc);
2511 }
2512 if (!glyph) {
2513 FT_Set_Charmap(face, freetype->symbol_map);
2514 glyph = FT_Get_Char_Index(face, uc);
2515 FT_Set_Charmap(face, freetype->unicode_map);
2516 if (!glyph && symbol && uc < 0x100)
2517 glyph = FT_Get_Char_Index(face, uc + 0xf000);
2518 }
2519 glyphs->glyphs[glyph_pos] = glyph;
2520 if (uc < QFreetypeFace::cmapCacheSize)
2521 freetype->cmapCache[uc] = glyph;
2522 }
2523 if (glyphs->glyphs[glyph_pos] || isIgnorableChar(uc))
2524 mappedGlyphs++;
2525 ++glyph_pos;
2526 }
2527 } else {
2528 FT_Face face = freetype->face;
2529 QStringIterator it(str, str + len);
2530 while (it.hasNext()) {
2531 uint uc = it.next();
2532 glyphs->glyphs[glyph_pos] = uc < QFreetypeFace::cmapCacheSize ? freetype->cmapCache[uc] : 0;
2533 if (!glyphs->glyphs[glyph_pos]) {
2534 {
2535 redo:
2536 glyph_t glyph = FT_Get_Char_Index(face, uc);
2537 if (!glyph && (uc == 0xa0 || uc == 0x9)) {
2538 uc = 0x20;
2539 goto redo;
2540 }
2541 glyphs->glyphs[glyph_pos] = glyph;
2542 if (uc < QFreetypeFace::cmapCacheSize)
2543 freetype->cmapCache[uc] = glyph;
2544 }
2545 }
2546 if (glyphs->glyphs[glyph_pos] || isIgnorableChar(uc))
2547 mappedGlyphs++;
2548 ++glyph_pos;
2549 }
2550 }
2551
2552 *nglyphs = glyph_pos;
2553 glyphs->numGlyphs = glyph_pos;
2554
2555 if (!(flags & GlyphIndicesOnly))
2556 recalcAdvances(glyphs, flags);
2557
2558 return mappedGlyphs;
2559}
2560
2561bool QFontEngineFT::shouldUseDesignMetrics(QFontEngine::ShaperFlags flags) const
2562{
2563 if (!FT_IS_SCALABLE(freetype->face))
2564 return false;
2565
2566 return supportsHorizontalSubPixelPositions() || (flags & DesignMetrics);
2567}
2568
2569QFixed QFontEngineFT::scaledBitmapMetrics(QFixed m) const
2570{
2571 return m * scalableBitmapScaleFactor;
2572}
2573
2574glyph_metrics_t QFontEngineFT::scaledBitmapMetrics(const glyph_metrics_t &m, const QTransform &t) const
2575{
2576 QTransform trans;
2577 trans.setMatrix(t.m11(), t.m12(), t.m13(),
2578 t.m21(), t.m22(), t.m23(),
2579 0, 0, t.m33());
2580 const qreal scaleFactor = scalableBitmapScaleFactor.toReal();
2581 trans.scale(scaleFactor, scaleFactor);
2582
2583 QRectF rect(m.x.toReal(), m.y.toReal(), m.width.toReal(), m.height.toReal());
2584 QPointF offset(m.xoff.toReal(), m.yoff.toReal());
2585
2586 rect = trans.mapRect(rect);
2587 offset = trans.map(offset);
2588
2589 glyph_metrics_t metrics;
2590 metrics.x = QFixed::fromReal(rect.x());
2591 metrics.y = QFixed::fromReal(rect.y());
2592 metrics.width = QFixed::fromReal(rect.width());
2593 metrics.height = QFixed::fromReal(rect.height());
2594 metrics.xoff = QFixed::fromReal(offset.x());
2595 metrics.yoff = QFixed::fromReal(offset.y());
2596 return metrics;
2597}
2598
2599void QFontEngineFT::recalcAdvances(QGlyphLayout *glyphs, QFontEngine::ShaperFlags flags) const
2600{
2601 FT_Face face = nullptr;
2602 bool design = shouldUseDesignMetrics(flags);
2603 for (int i = 0; i < glyphs->numGlyphs; i++) {
2604 Glyph *g = cacheEnabled ? defaultGlyphSet.getGlyph(glyphs->glyphs[i]) : nullptr;
2605 // Since we are passing Format_None to loadGlyph, use same default format logic as loadGlyph
2606 GlyphFormat acceptableFormat = (defaultFormat != Format_None) ? defaultFormat : Format_Mono;
2607 if (g && g->format == acceptableFormat) {
2608 glyphs->advances[i] = design ? QFixed::fromFixed(g->linearAdvance) : QFixed(g->advance);
2609 } else {
2610 if (!face)
2611 face = lockFace();
2612 g = loadGlyph(cacheEnabled ? &defaultGlyphSet : nullptr,
2613 glyphs->glyphs[i],
2614 QFixedPoint(),
2615 QColor(),
2616 Format_None,
2617 true);
2618 if (g)
2619 glyphs->advances[i] = design ? QFixed::fromFixed(g->linearAdvance) : QFixed(g->advance);
2620 else
2621 glyphs->advances[i] = design ? QFixed::fromFixed(face->glyph->linearHoriAdvance >> 10)
2622 : QFixed::fromFixed(face->glyph->metrics.horiAdvance).round();
2623 if (!cacheEnabled && g != &emptyGlyph)
2624 delete g;
2625 }
2626
2627 if (scalableBitmapScaleFactor != 1)
2628 glyphs->advances[i] *= scalableBitmapScaleFactor;
2629 }
2630 if (face)
2631 unlockFace();
2632}
2633
2634glyph_metrics_t QFontEngineFT::boundingBox(const QGlyphLayout &glyphs)
2635{
2636 FT_Face face = nullptr;
2637
2638 glyph_metrics_t overall;
2639 // initialize with line height, we get the same behaviour on all platforms
2640 if (!isScalableBitmap()) {
2641 overall.y = -ascent();
2642 overall.height = ascent() + descent();
2643 } else {
2644 overall.y = QFixed::fromFixed(-metrics.ascender);
2645 overall.height = QFixed::fromFixed(metrics.ascender - metrics.descender);
2646 }
2647
2648 QFixed ymax = 0;
2649 QFixed xmax = 0;
2650 for (int i = 0; i < glyphs.numGlyphs; i++) {
2651 // If shaping has found this should be ignored, ignore it.
2652 if (!glyphs.advances[i] || glyphs.attributes[i].dontPrint)
2653 continue;
2654 Glyph *g = cacheEnabled ? defaultGlyphSet.getGlyph(glyphs.glyphs[i]) : nullptr;
2655 if (!g) {
2656 if (!face)
2657 face = lockFace();
2658 g = loadGlyph(cacheEnabled ? &defaultGlyphSet : nullptr,
2659 glyphs.glyphs[i],
2660 QFixedPoint(),
2661 QColor(),
2662 Format_None,
2663 true);
2664 }
2665 if (g) {
2666 QFixed x = overall.xoff + glyphs.offsets[i].x + g->x;
2667 QFixed y = overall.yoff + glyphs.offsets[i].y - g->y;
2668 overall.x = qMin(overall.x, x);
2669 overall.y = qMin(overall.y, y);
2670 xmax = qMax(xmax, x.ceil() + g->width);
2671 ymax = qMax(ymax, y.ceil() + g->height);
2672 if (!cacheEnabled && g != &emptyGlyph)
2673 delete g;
2674 } else {
2675 int left = FLOOR(face->glyph->metrics.horiBearingX);
2676 int right = CEIL(face->glyph->metrics.horiBearingX + face->glyph->metrics.width);
2677 int top = CEIL(face->glyph->metrics.horiBearingY);
2678 int bottom = FLOOR(face->glyph->metrics.horiBearingY - face->glyph->metrics.height);
2679
2680 QFixed x = overall.xoff + glyphs.offsets[i].x - (-TRUNC(left));
2681 QFixed y = overall.yoff + glyphs.offsets[i].y - TRUNC(top);
2682 overall.x = qMin(overall.x, x);
2683 overall.y = qMin(overall.y, y);
2684 xmax = qMax(xmax, x + TRUNC(right - left));
2685 ymax = qMax(ymax, y + TRUNC(top - bottom));
2686 }
2687 overall.xoff += glyphs.effectiveAdvance(i);
2688 }
2689 overall.height = qMax(overall.height, ymax - overall.y);
2690 overall.width = xmax - overall.x;
2691
2692 if (face)
2693 unlockFace();
2694
2695 if (isScalableBitmap())
2696 overall = scaledBitmapMetrics(overall, QTransform());
2697 return overall;
2698}
2699
2700glyph_metrics_t QFontEngineFT::boundingBox(glyph_t glyph)
2701{
2702 FT_Face face = nullptr;
2703 glyph_metrics_t overall;
2704 Glyph *g = cacheEnabled ? defaultGlyphSet.getGlyph(glyph) : nullptr;
2705 if (!g) {
2706 face = lockFace();
2707 g = loadGlyph(cacheEnabled ? &defaultGlyphSet : nullptr,
2708 glyph,
2709 QFixedPoint(),
2710 QColor(),
2711 Format_None,
2712 true);
2713 }
2714 if (g) {
2715 overall.x = g->x;
2716 overall.y = -g->y;
2717 overall.width = g->width;
2718 overall.height = g->height;
2719 overall.xoff = g->advance;
2720 if (!cacheEnabled && g != &emptyGlyph)
2721 delete g;
2722 } else {
2723 int left = FLOOR(face->glyph->metrics.horiBearingX);
2724 int right = CEIL(face->glyph->metrics.horiBearingX + face->glyph->metrics.width);
2725 int top = CEIL(face->glyph->metrics.horiBearingY);
2726 int bottom = FLOOR(face->glyph->metrics.horiBearingY - face->glyph->metrics.height);
2727
2728 overall.width = TRUNC(right-left);
2729 overall.height = TRUNC(top-bottom);
2730 overall.x = TRUNC(left);
2731 overall.y = -TRUNC(top);
2732 overall.xoff = TRUNC(ROUND(face->glyph->advance.x));
2733 }
2734 if (face)
2735 unlockFace();
2736
2737 if (isScalableBitmap())
2738 overall = scaledBitmapMetrics(overall, QTransform());
2739 return overall;
2740}
2741
2742glyph_metrics_t QFontEngineFT::boundingBox(glyph_t glyph, const QTransform &matrix)
2743{
2744 return alphaMapBoundingBox(glyph, QFixedPoint(), matrix, QFontEngine::Format_None);
2745}
2746
2747glyph_metrics_t QFontEngineFT::alphaMapBoundingBox(glyph_t glyph,
2748 const QFixedPoint &subPixelPosition,
2749 const QTransform &matrix,
2750 QFontEngine::GlyphFormat format)
2751{
2752 // When rendering glyphs into a cache via the alphaMap* functions, we disable
2753 // outline drawing. To ensure the bounding box matches the rendered glyph, we
2754 // need to do the same here.
2755
2756 const bool needsImageTransform = !FT_IS_SCALABLE(freetype->face)
2757 && matrix.type() > QTransform::TxTranslate;
2758 if (needsImageTransform && format == QFontEngine::Format_Mono)
2759 format = QFontEngine::Format_A8;
2760 Glyph *g = loadGlyphFor(glyph, subPixelPosition, format, matrix, QColor(), true, true);
2761
2762 glyph_metrics_t overall;
2763 if (g) {
2764 overall.x = g->x;
2765 overall.y = -g->y;
2766 overall.width = g->width;
2767 overall.height = g->height;
2768 overall.xoff = g->advance;
2769 if (!cacheEnabled && g != &emptyGlyph)
2770 delete g;
2771 } else {
2772 FT_Face face = lockFace();
2773 int left = FLOOR(face->glyph->metrics.horiBearingX);
2774 int right = CEIL(face->glyph->metrics.horiBearingX + face->glyph->metrics.width);
2775 int top = CEIL(face->glyph->metrics.horiBearingY);
2776 int bottom = FLOOR(face->glyph->metrics.horiBearingY - face->glyph->metrics.height);
2777
2778 overall.width = TRUNC(right-left);
2779 overall.height = TRUNC(top-bottom);
2780 overall.x = TRUNC(left);
2781 overall.y = -TRUNC(top);
2782 overall.xoff = TRUNC(ROUND(face->glyph->advance.x));
2783 unlockFace();
2784 }
2785
2786 if (isScalableBitmap() || needsImageTransform)
2787 overall = scaledBitmapMetrics(overall, matrix);
2788 return overall;
2789}
2790
2791static inline QImage alphaMapFromGlyphData(QFontEngineFT::Glyph *glyph, QFontEngine::GlyphFormat glyphFormat)
2792{
2793 if (glyph == nullptr || glyph->height == 0 || glyph->width == 0)
2794 return QImage();
2795
2796 QImage::Format format = QImage::Format_Invalid;
2797 int bytesPerLine = -1;
2798 switch (glyphFormat) {
2799 case QFontEngine::Format_Mono:
2800 format = QImage::Format_Mono;
2801 bytesPerLine = ((glyph->width + 31) & ~31) >> 3;
2802 break;
2803 case QFontEngine::Format_A8:
2804 format = QImage::Format_Alpha8;
2805 bytesPerLine = (glyph->width + 3) & ~3;
2806 break;
2807 case QFontEngine::Format_A32:
2808 format = QImage::Format_RGB32;
2809 bytesPerLine = glyph->width * 4;
2810 break;
2811 default:
2812 Q_UNREACHABLE();
2813 };
2814
2815 QImage img(static_cast<const uchar *>(glyph->data), glyph->width, glyph->height, bytesPerLine, format);
2816 if (format == QImage::Format_Mono)
2817 img.setColor(1, QColor(Qt::white).rgba()); // Expands color table to 2 items; item 0 set to transparent.
2818 return img;
2819}
2820
2821QFontEngine::Glyph *QFontEngineFT::glyphData(glyph_t glyphIndex,
2822 const QFixedPoint &subPixelPosition,
2823 QFontEngine::GlyphFormat neededFormat,
2824 const QTransform &t)
2825{
2826 Q_ASSERT(cacheEnabled);
2827
2828 if (isBitmapFont())
2829 neededFormat = Format_Mono;
2830 else if (neededFormat == Format_None && defaultFormat != Format_None)
2831 neededFormat = defaultFormat;
2832 else if (neededFormat == Format_None)
2833 neededFormat = Format_A8;
2834
2835 Glyph *glyph = loadGlyphFor(glyphIndex, subPixelPosition, neededFormat, t, QColor());
2836 if (!glyph || !glyph->width || !glyph->height)
2837 return nullptr;
2838
2839 return glyph;
2840}
2841
2842static inline bool is2dRotation(const QTransform &t)
2843{
2844 return qFuzzyCompare(t.m11(), t.m22()) && qFuzzyCompare(t.m12(), -t.m21())
2845 && qFuzzyCompare(t.m11()*t.m22() - t.m12()*t.m21(), qreal(1.0));
2846}
2847
2848QFontEngineFT::Glyph *QFontEngineFT::loadGlyphFor(glyph_t g,
2849 const QFixedPoint &subPixelPosition,
2850 GlyphFormat format,
2851 const QTransform &t,
2852 QColor color,
2853 bool fetchBoundingBox,
2854 bool disableOutlineDrawing)
2855{
2856 QGlyphSet *glyphSet = loadGlyphSet(t);
2857 if (glyphSet != nullptr && glyphSet->outline_drawing && !disableOutlineDrawing && !fetchBoundingBox)
2858 return nullptr;
2859
2860 Glyph *glyph = glyphSet != nullptr ? glyphSet->getGlyph(g, subPixelPosition) : nullptr;
2861 if (!glyph || glyph->format != format || (!fetchBoundingBox && !glyph->data)) {
2862 QScopedValueRollback<HintStyle> saved_default_hint_style(default_hint_style);
2863
2864 // A scaled glyph can be hinted at the size it has on the device, if it's a pixel at least
2865 const qreal scaleX = qAbs(t.m11());
2866 const qreal scaleY = qAbs(t.m22());
2867 const bool hintAtDeviceSize = default_hint_style != HintNone
2868 && format != Format_ARGB // COLRv1 is drawn at the size of the engine
2869 && FT_IS_SCALABLE(freetype->face)
2870 && t.type() <= QTransform::TxScale
2871 && xsize * scaleX >= 64 && ysize * scaleY >= 64;
2872
2873 FT_Matrix m = this->matrix;
2874 if (hintAtDeviceSize) {
2875 // The size is scaled instead of the outline, lockFace() sets it back when needed
2876 freetype->lock();
2877 setFaceSize(qRound(xsize * scaleX), qRound(ysize * scaleY));
2878
2879 // The synthesized slant is applied to the outline at its new size, so the matrix
2880 // has to make up for a scale that is not uniform
2881 if (obliquen)
2882 m.xy = qRound(obliqueSlant * (scaleX / scaleY - 1));
2883
2884 // A size can't mirror the outline, that is left to the matrix too
2885 if (t.m11() < 0) {
2886 m.xx = -m.xx;
2887 m.xy = -m.xy;
2888 }
2889 if (t.m22() < 0) {
2890 m.yx = -m.yx;
2891 m.yy = -m.yy;
2892 }
2893 } else {
2894 if (t.type() >= QTransform::TxScale && !is2dRotation(t))
2895 default_hint_style = HintNone; // disable hinting if the glyphs are transformed
2896
2897 lockFace();
2898 FT_Matrix ftMatrix = glyphSet != nullptr ? glyphSet->transformationMatrix : QTransformToFTMatrix(t);
2899 FT_Matrix_Multiply(&ftMatrix, &m);
2900 }
2901 freetype->matrix = m;
2902 glyph = loadGlyph(glyphSet, g, subPixelPosition, color, format, false, disableOutlineDrawing);
2903 unlockFace();
2904 }
2905
2906 return glyph;
2907}
2908
2909QImage QFontEngineFT::alphaMapForGlyph(glyph_t g, const QFixedPoint &subPixelPosition)
2910{
2911 return alphaMapForGlyph(g, subPixelPosition, QTransform());
2912}
2913
2914QImage QFontEngineFT::alphaMapForGlyph(glyph_t g,
2915 const QFixedPoint &subPixelPosition,
2916 const QTransform &t)
2917{
2918 const bool needsImageTransform = !FT_IS_SCALABLE(freetype->face)
2919 && t.type() > QTransform::TxTranslate;
2920 const GlyphFormat neededFormat = antialias || needsImageTransform ? Format_A8 : Format_Mono;
2921
2922 Glyph *glyph = loadGlyphFor(g, subPixelPosition, neededFormat, t, QColor(), false, true);
2923
2924 QImage img = alphaMapFromGlyphData(glyph, neededFormat);
2925 if (needsImageTransform)
2926 img = img.transformed(t, Qt::FastTransformation);
2927 else
2928 img = img.copy();
2929
2930 if (!cacheEnabled && glyph != &emptyGlyph)
2931 delete glyph;
2932
2933 return img;
2934}
2935
2936QImage QFontEngineFT::alphaRGBMapForGlyph(glyph_t g,
2937 const QFixedPoint &subPixelPosition,
2938 const QTransform &t)
2939{
2940 if (t.type() > QTransform::TxRotate)
2941 return QFontEngine::alphaRGBMapForGlyph(g, subPixelPosition, t);
2942
2943 const bool needsImageTransform = !FT_IS_SCALABLE(freetype->face)
2944 && t.type() > QTransform::TxTranslate;
2945
2946
2947 const GlyphFormat neededFormat = Format_A32;
2948
2949 Glyph *glyph = loadGlyphFor(g, subPixelPosition, neededFormat, t, QColor(), false, true);
2950
2951 QImage img = alphaMapFromGlyphData(glyph, neededFormat);
2952 if (needsImageTransform)
2953 img = img.transformed(t, Qt::FastTransformation);
2954 else
2955 img = img.copy();
2956
2957 if (!cacheEnabled && glyph != &emptyGlyph)
2958 delete glyph;
2959
2960 if (!img.isNull())
2961 return img;
2962
2963 return QFontEngine::alphaRGBMapForGlyph(g, subPixelPosition, t);
2964}
2965
2966QImage QFontEngineFT::bitmapForGlyph(glyph_t g,
2967 const QFixedPoint &subPixelPosition,
2968 const QTransform &t,
2969 const QColor &color)
2970{
2971 Glyph *glyph = loadGlyphFor(g, subPixelPosition, defaultFormat, t, color);
2972 if (glyph == nullptr)
2973 return QImage();
2974
2975 QImage img;
2976 if (defaultFormat == GlyphFormat::Format_ARGB)
2977 img = QImage(glyph->data, glyph->width, glyph->height, QImage::Format_ARGB32_Premultiplied).copy();
2978 else if (defaultFormat == GlyphFormat::Format_Mono)
2979 img = QImage(glyph->data, glyph->width, glyph->height, QImage::Format_Mono).copy();
2980
2981 if (!img.isNull() && (scalableBitmapScaleFactor != 1 || (!t.isIdentity() && !isSmoothlyScalable))) {
2982 QTransform trans(t);
2983 const qreal scaleFactor = scalableBitmapScaleFactor.toReal();
2984 trans.scale(scaleFactor, scaleFactor);
2985 img = img.transformed(trans, Qt::SmoothTransformation);
2986 }
2987
2988 if (!cacheEnabled && glyph != &emptyGlyph)
2989 delete glyph;
2990
2991 return img;
2992}
2993
2994void QFontEngineFT::removeGlyphFromCache(glyph_t glyph)
2995{
2996 defaultGlyphSet.removeGlyphFromCache(glyph, QFixedPoint());
2997}
2998
2999int QFontEngineFT::glyphCount() const
3000{
3001 int count = 0;
3002 FT_Face face = lockFace();
3003 if (face) {
3004 count = int(std::clamp(face->num_glyphs, FT_Long(0), FT_Long(INT_MAX)));
3005 unlockFace();
3006 }
3007 return count;
3008}
3009
3010FT_Face QFontEngineFT::lockFace(Scaling scale) const
3011{
3012 freetype->lock();
3013 FT_Face face = freetype->face;
3014 if (scale == Unscaled) {
3015 if (FT_Set_Char_Size(face, face->units_per_EM << 6, face->units_per_EM << 6, 0, 0) == 0) {
3016 freetype->xsize = face->units_per_EM << 6;
3017 freetype->ysize = face->units_per_EM << 6;
3018 }
3019 } else {
3020 setFaceSize(xsize, ysize);
3021 }
3022 if (freetype->matrix.xx != matrix.xx ||
3023 freetype->matrix.yy != matrix.yy ||
3024 freetype->matrix.xy != matrix.xy ||
3025 freetype->matrix.yx != matrix.yx) {
3026 freetype->matrix = matrix;
3027 FT_Set_Transform(face, &freetype->matrix, nullptr);
3028 }
3029
3030 return face;
3031}
3032
3033void QFontEngineFT::setFaceSize(int xsize, int ysize) const
3034{
3035 if (freetype->xsize != xsize || freetype->ysize != ysize) {
3036 FT_Set_Char_Size(freetype->face, xsize, ysize, 0, 0);
3037 freetype->xsize = xsize;
3038 freetype->ysize = ysize;
3039 }
3040}
3041
3042void QFontEngineFT::unlockFace() const
3043{
3044 freetype->unlock();
3045}
3046
3047FT_Face QFontEngineFT::non_locked_face() const
3048{
3049 return freetype->face;
3050}
3051
3052
3053QFontEngineFT::QGlyphSet::QGlyphSet()
3054 : outline_drawing(false)
3055{
3056 transformationMatrix.xx = 0x10000;
3057 transformationMatrix.yy = 0x10000;
3058 transformationMatrix.xy = 0;
3059 transformationMatrix.yx = 0;
3060 memset(fast_glyph_data, 0, sizeof(fast_glyph_data));
3061 fast_glyph_count = 0;
3062}
3063
3064QFontEngineFT::QGlyphSet::~QGlyphSet()
3065{
3066 clear();
3067}
3068
3069void QFontEngineFT::QGlyphSet::clear()
3070{
3071 if (fast_glyph_count > 0) {
3072 for (int i = 0; i < 256; ++i) {
3073 if (fast_glyph_data[i]) {
3074 delete fast_glyph_data[i];
3075 fast_glyph_data[i] = nullptr;
3076 }
3077 }
3078 fast_glyph_count = 0;
3079 }
3080 qDeleteAll(glyph_data);
3081 glyph_data.clear();
3082}
3083
3084void QFontEngineFT::QGlyphSet::removeGlyphFromCache(glyph_t index,
3085 const QFixedPoint &subPixelPosition)
3086{
3087 if (useFastGlyphData(index, subPixelPosition)) {
3088 if (fast_glyph_data[index]) {
3089 delete fast_glyph_data[index];
3090 fast_glyph_data[index] = nullptr;
3091 if (fast_glyph_count > 0)
3092 --fast_glyph_count;
3093 }
3094 } else {
3095 delete glyph_data.take(GlyphAndSubPixelPosition(index, subPixelPosition));
3096 }
3097}
3098
3099void QFontEngineFT::QGlyphSet::setGlyph(glyph_t index,
3100 const QFixedPoint &subPixelPosition,
3101 Glyph *glyph)
3102{
3103 if (useFastGlyphData(index, subPixelPosition)) {
3104 if (!fast_glyph_data[index])
3105 ++fast_glyph_count;
3106 fast_glyph_data[index] = glyph;
3107 } else {
3108 glyph_data.insert(GlyphAndSubPixelPosition(index, subPixelPosition), glyph);
3109 }
3110}
3111
3112int QFontEngineFT::getPointInOutline(glyph_t glyph, int flags, quint32 point, QFixed *xpos, QFixed *ypos, quint32 *nPoints)
3113{
3114 lockFace();
3115 bool hsubpixel = true;
3116 int vfactor = 1;
3117 int load_flags = loadFlags(nullptr, Format_A8, flags, hsubpixel, vfactor);
3118 int result = freetype->getPointInOutline(glyph, load_flags, point, xpos, ypos, nPoints);
3119 unlockFace();
3120 return result;
3121}
3122
3123bool QFontEngineFT::initFromFontEngine(const QFontEngineFT *fe)
3124{
3125 if (!init(fe->faceId(), fe->antialias, fe->defaultFormat, fe->freetype))
3126 return false;
3127
3128 // Increase the reference of this QFreetypeFace since one more QFontEngineFT
3129 // will be using it
3130 freetype->ref.ref();
3131
3132 default_load_flags = fe->default_load_flags;
3133 default_hint_style = fe->default_hint_style;
3134 antialias = fe->antialias;
3135 transform = fe->transform;
3136 embolden = fe->embolden;
3137 obliquen = fe->obliquen;
3138 subpixelType = fe->subpixelType;
3139 lcdFilterType = fe->lcdFilterType;
3140 embeddedbitmap = fe->embeddedbitmap;
3141 verticalHintingOnly = fe->verticalHintingOnly;
3142
3143 return true;
3144}
3145
3146QFontEngine *QFontEngineFT::cloneWithSize(qreal pixelSize) const
3147{
3148 QFontDef fontDef(this->fontDef);
3149 fontDef.pixelSize = pixelSize;
3150 QFontEngineFT *fe = new QFontEngineFT(fontDef);
3151 if (!fe->initFromFontEngine(this)) {
3152 delete fe;
3153 return nullptr;
3154 } else {
3155 return fe;
3156 }
3157}
3158
3159Qt::HANDLE QFontEngineFT::handle() const
3160{
3161 return non_locked_face();
3162}
3163
3164QList<QFontVariableAxis> QFontEngineFT::variableAxes() const
3165{
3166 return freetype->variableAxes();
3167}
3168
3169QT_END_NAMESPACE
3170
3171#endif // QT_NO_FREETYPE
\inmodule QtCore
QHash< QFontEngine::FaceId, QFreetypeFace * > faces
QList< QFreetypeFace * > staleFaces
QHash< FaceStyle, int > faceIndices
Combined button and popup list for selecting options.
static bool hasTrueTypeInterpreterV40(FT_Library library)
static const QFontEngine::HintStyle ftInitialDefaultHintStyle
#define FLOOR(x)
static QFontEngine::SubpixelAntialiasingType subpixelAntialiasingTypeHint()
static FT_Matrix QTransformToFTMatrix(const QTransform &matrix)
static void transformBoundingBox(int *left, int *top, int *right, int *bottom, FT_Matrix *matrix)
static void convertRGBToARGB(const uchar *src, uint *dst, int width, int height, int src_pitch, bool bgr)
QtFreetypeData * qt_getFreetypeData()
static bool calculateActualItalic(QFreetypeFace *freetypeFace, FT_Face face, QFontEngine::FaceId faceId)
#define CEIL(x)
static bool is2dRotation(const QTransform &t)
static QImage alphaMapFromGlyphData(QFontEngineFT::Glyph *glyph, QFontEngine::GlyphFormat glyphFormat)
static QFontEngineFT::Glyph emptyGlyph
QByteArray qt_fontdata_from_index(int)
size_t qHash(const QtFreetypeData::FaceStyle &style, size_t seed)
static bool ft_getSfntTable(void *user_data, uint tag, uchar *buffer, uint *length)
void qt_addBitmapToPath(qreal x0, qreal y0, const uchar *image_data, int bpl, int w, int h, QPainterPath *path)
static int computeFaceIndex(const QString &faceFileName, const QString &styleName)
bool operator==(const QtFreetypeData::FaceStyle &style1, const QtFreetypeData::FaceStyle &style2)
FT_Library qt_getFreetype()
static FT_UShort calculateActualWeight(QFreetypeFace *freetypeFace, FT_Face face, QFontEngine::FaceId faceId)
#define GLYPH2PATH_DEBUG
static void convertRGBToARGB_V(const uchar *src, uint *dst, int width, int height, int src_pitch, bool bgr)
static void scaleOutline(FT_Face face, FT_GlyphSlot g, FT_Fixed x_scale, FT_Fixed y_scale)
static const FT_Fixed obliqueSlant
static void dont_delete(void *)
#define ROUND(x)
static bool areMetricsTooLarge(const QFontEngineFT::GlyphInfo &info)
#define TRUNC(x)
FaceStyle(QString faceFileName, QString styleName)