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

Provided by KDAB

Privacy Policy
Start learning QML with our Intro Training
Find out more

source code of qtbase/src/gui/text/freetype/qfontengine_ft.cpp